diff options
Diffstat (limited to 'src/output')
-rw-r--r-- | src/output/AlsaOutputPlugin.cxx (renamed from src/output/alsa_output_plugin.c) | 212 | ||||
-rw-r--r-- | src/output/AlsaOutputPlugin.hxx (renamed from src/output/alsa_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/HttpdClient.cxx | 444 | ||||
-rw-r--r-- | src/output/HttpdClient.hxx | 186 | ||||
-rw-r--r-- | src/output/HttpdInternal.hxx (renamed from src/output/httpd_internal.h) | 122 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.cxx | 570 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.hxx (renamed from src/output/httpd_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/NullOutputPlugin.cxx (renamed from src/output/null_output_plugin.c) | 43 | ||||
-rw-r--r-- | src/output/NullOutputPlugin.hxx (renamed from src/output/null_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/OSXOutputPlugin.cxx (renamed from src/output/osx_output_plugin.c) | 89 | ||||
-rw-r--r-- | src/output/OSXOutputPlugin.hxx (renamed from src/output/osx_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/OssOutputPlugin.cxx (renamed from src/output/oss_output_plugin.c) | 67 | ||||
-rw-r--r-- | src/output/OssOutputPlugin.hxx (renamed from src/output/oss_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/RoarOutputPlugin.cxx (renamed from src/output/roar_output_plugin.c) | 150 | ||||
-rw-r--r-- | src/output/RoarOutputPlugin.hxx (renamed from src/output/roar_output_plugin.h) | 10 | ||||
-rw-r--r-- | src/output/WinmmOutputPlugin.cxx (renamed from src/output/winmm_output_plugin.c) | 87 | ||||
-rw-r--r-- | src/output/WinmmOutputPlugin.hxx (renamed from src/output/winmm_output_plugin.h) | 15 | ||||
-rw-r--r-- | src/output/fifo_output_plugin.c | 1 | ||||
-rw-r--r-- | src/output/httpd_client.c | 764 | ||||
-rw-r--r-- | src/output/httpd_client.h | 71 | ||||
-rw-r--r-- | src/output/httpd_output_plugin.c | 623 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.c | 4 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.h | 12 | ||||
-rw-r--r-- | src/output/shout_output_plugin.c | 104 |
24 files changed, 1735 insertions, 1869 deletions
diff --git a/src/output/alsa_output_plugin.c b/src/output/AlsaOutputPlugin.cxx index d8b184273..c87c4cb99 100644 --- a/src/output/alsa_output_plugin.c +++ b/src/output/AlsaOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,14 +18,16 @@ */ #include "config.h" -#include "alsa_output_plugin.h" +#include "AlsaOutputPlugin.hxx" #include "output_api.h" -#include "mixer_list.h" +#include "MixerList.hxx" #include "pcm_export.h" #include <glib.h> #include <alsa/asoundlib.h> +#include <string> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "alsa" @@ -43,14 +45,16 @@ enum { typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, snd_pcm_uframes_t size); -struct alsa_data { +struct AlsaOutput { struct audio_output base; - struct pcm_export_state export; + struct pcm_export_state pcm_export; - /** the configured name of the ALSA device; NULL for the - default device */ - char *device; + /** + * The configured name of the ALSA device; empty for the + * default device + */ + std::string device; /** use memory mapped I/O? */ bool use_mmap; @@ -101,6 +105,25 @@ struct alsa_data { * The number of frames written in the current period. */ snd_pcm_uframes_t period_position; + + /** + * This buffer gets allocated after opening the ALSA device. + * It contains silence samples, enough to fill one period (see + * #period_frames). + */ + void *silence; + + AlsaOutput():mode(0), writei(snd_pcm_writei) { + } + + bool Init(const config_param *param, GError **error_r) { + return ao_base_init(&base, &alsa_output_plugin, + param, error_r); + } + + void Deinit() { + ao_base_finish(&base); + } }; /** @@ -113,26 +136,15 @@ alsa_output_quark(void) } static const char * -alsa_device(const struct alsa_data *ad) +alsa_device(const AlsaOutput *ad) { - return ad->device != NULL ? ad->device : default_device; -} - -static struct alsa_data * -alsa_data_new(void) -{ - struct alsa_data *ret = g_new(struct alsa_data, 1); - - ret->mode = 0; - ret->writei = snd_pcm_writei; - - return ret; + return ad->device.empty() ? default_device : ad->device.c_str(); } static void -alsa_configure(struct alsa_data *ad, const struct config_param *param) +alsa_configure(AlsaOutput *ad, const struct config_param *param) { - ad->device = config_dup_block_string(param, "device", NULL); + ad->device = config_get_block_string(param, "device", ""); ad->use_mmap = config_get_block_bool(param, "use_mmap", false); @@ -161,10 +173,10 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param) static struct audio_output * alsa_init(const struct config_param *param, GError **error_r) { - struct alsa_data *ad = alsa_data_new(); + AlsaOutput *ad = new AlsaOutput(); - if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { - g_free(ad); + if (!ad->Init(param, error_r)) { + delete ad; return NULL; } @@ -176,12 +188,10 @@ alsa_init(const struct config_param *param, GError **error_r) static void alsa_finish(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; - ao_base_finish(&ad->base); - - g_free(ad->device); - g_free(ad); + ad->Deinit(); + delete ad; /* free libasound's config cache */ snd_config_update_free_global(); @@ -190,18 +200,18 @@ alsa_finish(struct audio_output *ao) static bool alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; - pcm_export_init(&ad->export); + pcm_export_init(&ad->pcm_export); return true; } static void alsa_output_disable(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; - pcm_export_deinit(&ad->export); + pcm_export_deinit(&ad->pcm_export); } static bool @@ -349,7 +359,8 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, { /* try the input format first */ - int err = alsa_output_try_format(pcm, hwparams, audio_format->format, + int err = alsa_output_try_format(pcm, hwparams, + sample_format(audio_format->format), packed_r, reverse_endian_r); /* if unsupported by the hardware, try other formats */ @@ -383,15 +394,11 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, * the configured settings and the audio format. */ static bool -alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, +alsa_setup(AlsaOutput *ad, struct audio_format *audio_format, bool *packed_r, bool *reverse_endian_r, GError **error) { - snd_pcm_hw_params_t *hwparams; - snd_pcm_sw_params_t *swparams; unsigned int sample_rate = audio_format->sample_rate; unsigned int channels = audio_format->channels; - snd_pcm_uframes_t alsa_buffer_size; - snd_pcm_uframes_t alsa_period_size; int err; const char *cmd = NULL; int retry = MPD_ALSA_RETRY_NR; @@ -401,6 +408,7 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, period_time_ro = period_time = ad->period_time; configure_hw: /* configure HW params */ + snd_pcm_hw_params_t *hwparams; snd_pcm_hw_params_alloca(&hwparams); cmd = "snd_pcm_hw_params_any"; err = snd_pcm_hw_params_any(ad->pcm, hwparams); @@ -434,7 +442,7 @@ configure_hw: g_set_error(error, alsa_output_quark(), err, "ALSA device \"%s\" does not support format %s: %s", alsa_device(ad), - sample_format_to_string(audio_format->format), + sample_format_to_string(sample_format(audio_format->format)), snd_strerror(-err)); return false; } @@ -525,11 +533,13 @@ configure_hw: if (retry != MPD_ALSA_RETRY_NR) g_debug("ALSA period_time set to %d\n", period_time); + snd_pcm_uframes_t alsa_buffer_size; cmd = "snd_pcm_hw_params_get_buffer_size"; err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); if (err < 0) goto error; + snd_pcm_uframes_t alsa_period_size; cmd = "snd_pcm_hw_params_get_period_size"; err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, NULL); @@ -537,6 +547,7 @@ configure_hw: goto error; /* configure SW params */ + snd_pcm_sw_params_t *swparams; snd_pcm_sw_params_alloca(&swparams); cmd = "snd_pcm_sw_params_current"; @@ -576,6 +587,11 @@ configure_hw: ad->period_frames = alsa_period_size; ad->period_position = 0; + ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm, + alsa_period_size)); + snd_pcm_format_set_silence(format, ad->silence, + alsa_period_size * channels); + return true; error: @@ -586,7 +602,7 @@ error: } static bool -alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, +alsa_setup_dsd(AlsaOutput *ad, struct audio_format *audio_format, bool *shift8_r, bool *packed_r, bool *reverse_endian_r, GError **error_r) { @@ -619,6 +635,7 @@ alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, g_set_error(error_r, alsa_output_quark(), 0, "Failed to configure DSD-over-USB on ALSA device \"%s\"", alsa_device(ad)); + g_free(ad->silence); return false; } @@ -626,7 +643,7 @@ alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, } static bool -alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, +alsa_setup_or_dsd(AlsaOutput *ad, struct audio_format *audio_format, GError **error_r) { bool shift8 = false, packed, reverse_endian; @@ -642,8 +659,9 @@ alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, if (!success) return false; - pcm_export_open(&ad->export, - audio_format->format, audio_format->channels, + pcm_export_open(&ad->pcm_export, + sample_format(audio_format->format), + audio_format->channels, dsd_usb, shift8, packed, reverse_endian); return true; } @@ -651,12 +669,10 @@ alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, static bool alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct alsa_data *ad = (struct alsa_data *)ao; - int err; - bool success; + AlsaOutput *ad = (AlsaOutput *)ao; - err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); + int err = snd_pcm_open(&ad->pcm, alsa_device(ad), + SND_PCM_STREAM_PLAYBACK, ad->mode); if (err < 0) { g_set_error(error, alsa_output_quark(), err, "Failed to open ALSA device \"%s\": %s", @@ -667,20 +683,29 @@ alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **e g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), snd_pcm_type_name(snd_pcm_type(ad->pcm))); - success = alsa_setup_or_dsd(ad, audio_format, error); - if (!success) { + if (!alsa_setup_or_dsd(ad, audio_format, error)) { snd_pcm_close(ad->pcm); return false; } ad->in_frame_size = audio_format_frame_size(audio_format); - ad->out_frame_size = pcm_export_frame_size(&ad->export, audio_format); + ad->out_frame_size = pcm_export_frame_size(&ad->pcm_export, + audio_format); return true; } +/** + * Write silence to the ALSA device. + */ +static void +alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes) +{ + ad->writei(ad->pcm, ad->silence, nframes); +} + static int -alsa_recover(struct alsa_data *ad, int err) +alsa_recover(AlsaOutput *ad, int err) { if (err == -EPIPE) { g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad)); @@ -701,6 +726,26 @@ alsa_recover(struct alsa_data *ad, int err) case SND_PCM_STATE_XRUN: ad->period_position = 0; err = snd_pcm_prepare(ad->pcm); + + if (err == 0) { + /* this works around a driver bug observed on + the Raspberry Pi: after snd_pcm_drop(), the + whole ring buffer must be invalidated, but + the snd_pcm_prepare() call above makes the + driver play random data that just happens + to be still in the buffer; by adding and + cancelling some silence, this bug does not + occur */ + alsa_write_silence(ad, ad->period_frames); + + /* cancel the silence data right away to avoid + increasing latency; even though this + function call invalidates the portion of + silence, the driver seems to avoid the + bug */ + snd_pcm_reset(ad->pcm); + } + break; case SND_PCM_STATE_DISCONNECTED: break; @@ -719,7 +764,7 @@ alsa_recover(struct alsa_data *ad, int err) static void alsa_drain(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) return; @@ -729,20 +774,7 @@ alsa_drain(struct audio_output *ao) period */ snd_pcm_uframes_t nframes = ad->period_frames - ad->period_position; - size_t nbytes = nframes * ad->out_frame_size; - void *buffer = g_malloc(nbytes); - snd_pcm_hw_params_t *params; - snd_pcm_format_t format; - unsigned channels; - - snd_pcm_hw_params_alloca(¶ms); - snd_pcm_hw_params_current(ad->pcm, params); - snd_pcm_hw_params_get_format(params, &format); - snd_pcm_hw_params_get_channels(params, &channels); - - snd_pcm_format_set_silence(format, buffer, nframes * channels); - ad->writei(ad->pcm, buffer, nframes); - g_free(buffer); + alsa_write_silence(ad, nframes); } snd_pcm_drain(ad->pcm); @@ -753,7 +785,7 @@ alsa_drain(struct audio_output *ao) static void alsa_cancel(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; ad->period_position = 0; @@ -763,20 +795,21 @@ alsa_cancel(struct audio_output *ao) static void alsa_close(struct audio_output *ao) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; snd_pcm_close(ad->pcm); + g_free(ad->silence); } static size_t alsa_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; assert(size % ad->in_frame_size == 0); - chunk = pcm_export(&ad->export, chunk, size, &size); + chunk = pcm_export(&ad->pcm_export, chunk, size, &size); assert(size % ad->out_frame_size == 0); @@ -789,7 +822,7 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size, % ad->period_frames; size_t bytes_written = ret * ad->out_frame_size; - return pcm_export_source_size(&ad->export, + return pcm_export_source_size(&ad->pcm_export, bytes_written); } @@ -803,17 +836,20 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size, } const struct audio_output_plugin alsa_output_plugin = { - .name = "alsa", - .test_default_device = alsa_test_default_device, - .init = alsa_init, - .finish = alsa_finish, - .enable = alsa_output_enable, - .disable = alsa_output_disable, - .open = alsa_open, - .play = alsa_play, - .drain = alsa_drain, - .cancel = alsa_cancel, - .close = alsa_close, - - .mixer_plugin = &alsa_mixer_plugin, + "alsa", + alsa_test_default_device, + alsa_init, + alsa_finish, + alsa_output_enable, + alsa_output_disable, + alsa_open, + alsa_close, + nullptr, + nullptr, + alsa_play, + alsa_drain, + alsa_cancel, + nullptr, + + &alsa_mixer_plugin, }; diff --git a/src/output/alsa_output_plugin.h b/src/output/AlsaOutputPlugin.hxx index daa1f3615..dc7e639a8 100644 --- a/src/output/alsa_output_plugin.h +++ b/src/output/AlsaOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ALSA_OUTPUT_PLUGIN_H -#define MPD_ALSA_OUTPUT_PLUGIN_H +#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX +#define MPD_ALSA_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin alsa_output_plugin; diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 000000000..0a00ee2f9 --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,444 @@ +/* + * 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 "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/fifo_buffer.h" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "SocketError.hxx" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (current_page != nullptr) + current_page->Unref(); + + for (auto page : pages) + page->Unref(); + } + + if (metadata) + metadata->Unref(); +} + +void +HttpdClient::Close() +{ + httpd->RemoveClient(*this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd->mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + current_page = nullptr; + + httpd->SendHeader(*this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (strncmp(line, "GET /", 5) != 0) { + /* only GET is supported */ + g_warning("malformed request line from client"); + return false; + } + + line = strchr(line + 5, ' '); + if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + BeginResponse(); + return true; + } + + if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024]; + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd->content_type); + + } else if (metadata_requested) { + gchar *metadata_header; + + metadata_header = + icy_server_metadata_header(httpd->name, httpd->genre, + httpd->website, + httpd->content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd->content_type); + } + + ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer)); + if (gcc_unlikely(nbytes < 0)) { + const SocketErrorMessage msg; + g_warning("failed to write to client: %s", (const char *)msg); + Close(); + return false; + } + + return true; +} + +HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, + bool _metadata_supported) + :BufferedSocket(_fd, _loop), + httpd(_httpd), + state(REQUEST), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ +} + +size_t +HttpdClient::GetQueueSize() const +{ + if (state != RESPONSE) + return 0; + + size_t size = 0; + for (auto page : pages) + size += page->size; + return size; +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + for (auto page : pages) + page->Unref(); + pages.clear(); + + if (current_page == nullptr) + CancelWrite(); +} + +ssize_t +HttpdClient::TryWritePage(const Page &page, size_t position) +{ + assert(position < page.size); + + return Write(page.data + position, page.size - position); +} + +ssize_t +HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n) +{ + return n >= 0 + ? Write(page.data + position, n) + : TryWritePage(page, position); +} + +ssize_t +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::TryWrite() +{ + const ScopeLock protect(httpd->mutex); + + assert(state == RESPONSE); + + if (current_page == nullptr) { + if (pages.empty()) { + /* another thread has removed the event source + while this thread was waiting for + httpd->mutex */ + CancelWrite(); + return true; + } + + current_page = pages.front(); + pages.pop_front(); + current_position = 0; + } + + const ssize_t bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + if (!metadata_sent) { + ssize_t nbytes = TryWritePage(*metadata, + metadata_current_position); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_current_position += nbytes; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + guchar empty_data = 0; + + ssize_t nbytes = Write(&empty_data, 1); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_fill = 0; + metadata_current_position = 0; + } + } else { + ssize_t nbytes = + TryWritePageN(*current_page, current_position, + bytes_to_write); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + g_warning("failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + current_position += nbytes; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += nbytes; + + if (current_position >= current_page->size) { + current_page->Unref(); + current_page = nullptr; + + if (pages.empty()) + /* all pages are sent: remove the + event source */ + CancelWrite(); + } + } + + return true; +} + +void +HttpdClient::PushPage(Page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + page->Ref(); + pages.push_back(page); + + ScheduleWrite(); +} + +void +HttpdClient::PushMetaData(Page *page) +{ + if (metadata) { + metadata->Unref(); + metadata = nullptr; + } + + g_return_if_fail (page); + + page->Ref(); + metadata = page; + metadata_sent = false; +} + +bool +HttpdClient::OnSocketReady(unsigned flags) +{ + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (flags & WRITE) + if (!TryWrite()) + return false; + + return true; +} + +BufferedSocket::InputResult +HttpdClient::OnSocketInput(const void *data, size_t length) +{ + if (state == RESPONSE) { + g_warning("unexpected input from client"); + LockClose(); + return InputResult::CLOSED; + } + + const char *line = (const char *)data; + const char *newline = (const char *)memchr(line, '\n', length); + if (newline == nullptr) + return InputResult::MORE; + + ConsumeInput(newline + 1 - line); + + if (newline > line && newline[-1] == '\r') + --newline; + + /* terminate the string at the end of the line; the const_cast + is a dirty hack */ + *const_cast<char *>(newline) = 0; + + if (!HandleLine(line)) { + assert(state == RESPONSE); + LockClose(); + return InputResult::CLOSED; + } + + if (state == RESPONSE && !SendResponse()) + return InputResult::CLOSED; + + return InputResult::AGAIN; +} + +void +HttpdClient::OnSocketError(GError *error) +{ + g_warning("error on HTTP client: %s", error->message); + g_error_free(error); +} + +void +HttpdClient::OnSocketClosed() +{ + LockClose(); +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 000000000..46196d2e3 --- /dev/null +++ b/src/output/HttpdClient.hxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2013 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_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "event/BufferedSocket.hxx" +#include "gcc.h" + +#include <list> + +#include <stddef.h> + +struct HttpdOutput; +class Page; + +class HttpdClient final : public BufferedSocket { + /** + * The httpd output object this client is connected to. + */ + HttpdOutput *const httpd; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #Page objects to be sent to the client. + */ + std::list<Page *> pages; + + /** + * The #page which is currently being sent to the client. + */ + Page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + guint metaint; + + /** + * The metadata as #Page which is currently being sent to the client. + */ + Page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + guint metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop, + bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #HttpdOutput object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Returns the total size of this client's page queue. + */ + gcc_pure + size_t GetQueueSize() const; + + /** + * Clears the page queue. + */ + void CancelQueue(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + ssize_t GetBytesTillMetaData() const; + + ssize_t TryWritePage(const Page &page, size_t position); + ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n); + + bool TryWrite(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(Page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(Page *page); + +protected: + virtual bool OnSocketReady(unsigned flags) override; + virtual InputResult OnSocketInput(const void *data, + size_t length) override; + virtual void OnSocketError(GError *error) override; + virtual void OnSocketClosed() override; +}; + +#endif diff --git a/src/output/httpd_internal.h b/src/output/HttpdInternal.hxx index 5dcb8ab9b..4b526bcde 100644 --- a/src/output/httpd_internal.h +++ b/src/output/HttpdInternal.hxx @@ -27,14 +27,18 @@ #include "output_internal.h" #include "timer.h" +#include "thread/Mutex.hxx" +#include "event/ServerSocket.hxx" -#include <glib.h> +#include <forward_list> -#include <stdbool.h> +struct config_param; +class EventLoop; +class ServerSocket; +class HttpdClient; +class Page; -struct httpd_client; - -struct httpd_output { +struct HttpdOutput final : private ServerSocket { struct audio_output base; /** @@ -65,7 +69,7 @@ struct httpd_output { * This mutex protects the listener socket and the client * list. */ - GMutex *mutex; + mutable Mutex mutex; /** * A #timer object to synchronize this output with the @@ -74,19 +78,14 @@ struct httpd_output { struct timer *timer; /** - * The listener socket. - */ - struct server_socket *server_socket; - - /** * The header page, which is sent to every client on connect. */ - struct page *header; + Page *header; /** * The metadata, which is sent to every client. */ - struct page *metadata; + Page *metadata; /** * The configured name. @@ -105,7 +104,7 @@ struct httpd_output { * A linked list containing all clients which are currently * connected. */ - GList *clients; + std::forward_list<HttpdClient> clients; /** * A temporary buffer for the httpd_output_read_page() @@ -118,21 +117,88 @@ struct httpd_output { * at the same time. */ guint clients_max, clients_cnt; -}; -/** - * Removes a client from the httpd_output.clients linked list. - */ -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); + HttpdOutput(EventLoop &_loop); + ~HttpdOutput(); -/** - * Sends the encoder header to the client. This is called right after - * the response headers have been sent. - */ -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); + bool Configure(const config_param *param, GError **error_r); + + bool Bind(GError **error_r); + void Unbind(); + + /** + * Caller must lock the mutex. + */ + bool OpenEncoder(struct audio_format *audio_format, + GError **error_r); + + /** + * Caller must lock the mutex. + */ + bool Open(struct audio_format *audio_format, GError **error_r); + + /** + * Caller must lock the mutex. + */ + void Close(); + + /** + * Check whether there is at least one client. + * + * Caller must lock the mutex. + */ + gcc_pure + bool HasClients() const { + return !clients.empty(); + } + + /** + * Check whether there is at least one client. + */ + gcc_pure + bool LockHasClients() const { + const ScopeLock protect(mutex); + return HasClients(); + } + + void AddClient(int fd); + + /** + * Removes a client from the httpd_output.clients linked list. + */ + void RemoveClient(HttpdClient &client); + + /** + * Sends the encoder header to the client. This is called + * right after the response headers have been sent. + */ + void SendHeader(HttpdClient &client) const; + + /** + * Reads data from the encoder (as much as available) and + * returns it as a new #page object. + */ + Page *ReadPage(); + + /** + * Broadcasts a page struct to all clients. + * + * Mutext must not be locked. + */ + void BroadcastPage(Page *page); + + /** + * Broadcasts data from the encoder to all clients. + */ + void BroadcastFromEncoder(); + + bool EncodeAndPlay(const void *chunk, size_t size, GError **error_r); + + void SendTag(const struct tag *tag); + +private: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) override; +}; #endif diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx new file mode 100644 index 000000000..cb515e657 --- /dev/null +++ b/src/output/HttpdOutputPlugin.cxx @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2003-2013 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 "HttpdOutputPlugin.hxx" +#include "HttpdInternal.hxx" +#include "HttpdClient.hxx" +#include "output_api.h" +#include "encoder_plugin.h" +#include "encoder_list.h" +#include "resolver.h" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "fd_util.h" +#include "Main.hxx" + +#include <assert.h> + +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +#ifdef HAVE_LIBWRAP +#include <sys/socket.h> /* needed for AF_UNIX */ +#include <tcpd.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +/** + * The quark used for GError.domain. + */ +static inline GQuark +httpd_output_quark(void) +{ + return g_quark_from_static_string("httpd_output"); +} + +inline +HttpdOutput::HttpdOutput(EventLoop &_loop) + :ServerSocket(_loop), + encoder(nullptr), unflushed_input(0), + metadata(nullptr) +{ +} + +HttpdOutput::~HttpdOutput() +{ + if (metadata != nullptr) + metadata->Unref(); + + if (encoder != nullptr) + encoder_finish(encoder); + +} + +inline bool +HttpdOutput::Bind(GError **error_r) +{ + open = false; + + const ScopeLock protect(mutex); + return ServerSocket::Open(error_r); +} + +inline void +HttpdOutput::Unbind() +{ + assert(!open); + + const ScopeLock protect(mutex); + ServerSocket::Close(); +} + +inline bool +HttpdOutput::Configure(const config_param *param, GError **error_r) +{ + /* read configuration */ + name = config_get_block_string(param, "name", "Set name in config"); + genre = config_get_block_string(param, "genre", "Set genre in config"); + website = config_get_block_string(param, "website", + "Set website in config"); + + guint port = config_get_block_unsigned(param, "port", 8000); + + const char *encoder_name = + config_get_block_string(param, "encoder", "vorbis"); + const struct encoder_plugin *encoder_plugin = + encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + g_set_error(error_r, httpd_output_quark(), 0, + "No such encoder: %s", encoder_name); + return false; + } + + clients_max = config_get_block_unsigned(param,"max_clients", 0); + + /* set up bind_to_address */ + + const char *bind_to_address = + config_get_block_string(param, "bind_to_address", NULL); + bool success = bind_to_address != NULL && + strcmp(bind_to_address, "any") != 0 + ? AddHost(bind_to_address, port, error_r) + : AddPort(port, error_r); + if (!success) + return false; + + /* initialize encoder */ + + encoder = encoder_init(encoder_plugin, param, error_r); + if (encoder == nullptr) + return false; + + /* determine content type */ + content_type = encoder_get_mime_type(encoder); + if (content_type == nullptr) + content_type = "application/octet-stream"; + + return true; +} + +static struct audio_output * +httpd_output_init(const struct config_param *param, + GError **error_r) +{ + HttpdOutput *httpd = new HttpdOutput(*main_loop); + + if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, + error_r)) { + delete httpd; + return nullptr; + } + + if (!httpd->Configure(param, error_r)) { + ao_base_finish(&httpd->base); + delete httpd; + return nullptr; + } + + return &httpd->base; +} + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +static inline constexpr HttpdOutput * +Cast(audio_output *ao) +{ + return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base)); +} + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +static void +httpd_output_finish(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + ao_base_finish(&httpd->base); + delete httpd; +} + +/** + * Creates a new #HttpdClient object and adds it into the + * HttpdOutput.clients linked list. + */ +inline void +HttpdOutput::AddClient(int fd) +{ + clients.emplace_front(this, fd, GetEventLoop(), + encoder->plugin->tag == NULL); + ++clients_cnt; + + /* pass metadata to client */ + if (metadata != nullptr) + clients.front().PushMetaData(metadata); +} + +void +HttpdOutput::OnAccept(int fd, const sockaddr &address, + size_t address_length, gcc_unused int uid) +{ + /* the listener socket has become readable - a client has + connected */ + +#ifdef HAVE_LIBWRAP + if (address.sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(&address, address_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_warning("libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + g_free(hostaddr); + close_socket(fd); + return; + } + + g_free(hostaddr); + } +#else + (void)address; + (void)address_length; +#endif /* HAVE_WRAP */ + + const ScopeLock protect(mutex); + + if (fd >= 0) { + /* can we allow additional client */ + if (open && (clients_max == 0 || clients_cnt < clients_max)) + AddClient(fd); + else + close_socket(fd); + } else if (fd < 0 && errno != EINTR) { + g_warning("accept() failed: %s", g_strerror(errno)); + } +} + +Page * +HttpdOutput::ReadPage() +{ + if (unflushed_input >= 65536) { + /* we have fed a lot of input into the encoder, but it + didn't give anything back yet - flush now to avoid + buffer underruns */ + encoder_flush(encoder, NULL); + unflushed_input = 0; + } + + size_t size = 0; + do { + size_t nbytes = encoder_read(encoder, + buffer + size, + sizeof(buffer) - size); + if (nbytes == 0) + break; + + unflushed_input = 0; + + size += nbytes; + } while (size < sizeof(buffer)); + + if (size == 0) + return NULL; + + return Page::Copy(buffer, size); +} + +static bool +httpd_output_enable(struct audio_output *ao, GError **error_r) +{ + HttpdOutput *httpd = Cast(ao); + + return httpd->Bind(error_r); +} + +static void +httpd_output_disable(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + httpd->Unbind(); +} + +inline bool +HttpdOutput::OpenEncoder(struct audio_format *audio_format, GError **error) +{ + if (!encoder_open(encoder, audio_format, error)) + return false; + + /* we have to remember the encoder header, i.e. the first + bytes of encoder output after opening it, because it has to + be sent to every new client */ + header = ReadPage(); + + unflushed_input = 0; + + return true; +} + +inline bool +HttpdOutput::Open(struct audio_format *audio_format, GError **error_r) +{ + assert(!open); + assert(clients.empty()); + + /* open the encoder */ + + if (!OpenEncoder(audio_format, error_r)) + return false; + + /* initialize other attributes */ + + clients_cnt = 0; + timer = timer_new(audio_format); + + open = true; + + return true; +} + +static bool +httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + HttpdOutput *httpd = Cast(ao); + + assert(httpd->clients.empty()); + + const ScopeLock protect(httpd->mutex); + return httpd->Open(audio_format, error); +} + +inline void +HttpdOutput::Close() +{ + assert(open); + + open = false; + + timer_free(timer); + + clients.clear(); + + if (header != NULL) + header->Unref(); + + encoder_close(encoder); +} + +static void +httpd_output_close(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + const ScopeLock protect(httpd->mutex); + httpd->Close(); +} + +void +HttpdOutput::RemoveClient(HttpdClient &client) +{ + assert(clients_cnt > 0); + + for (auto prev = clients.before_begin(), i = std::next(prev);; + prev = i, i = std::next(prev)) { + assert(i != clients.end()); + if (&*i == &client) { + clients.erase_after(prev); + clients_cnt--; + break; + } + } +} + +void +HttpdOutput::SendHeader(HttpdClient &client) const +{ + if (header != NULL) + client.PushPage(header); +} + +static unsigned +httpd_output_delay(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + if (!httpd->LockHasClients() && httpd->base.pause) { + /* if there's no client and this output is paused, + then httpd_output_pause() will not do anything, it + will not fill the buffer and it will not update the + timer; therefore, we reset the timer here */ + timer_reset(httpd->timer); + + /* some arbitrary delay that is long enough to avoid + consuming too much CPU, and short enough to notice + new clients quickly enough */ + return 1000; + } + + return httpd->timer->started + ? timer_delay(httpd->timer) + : 0; +} + +void +HttpdOutput::BroadcastPage(Page *page) +{ + assert(page != NULL); + + const ScopeLock protect(mutex); + for (auto &client : clients) + client.PushPage(page); +} + +void +HttpdOutput::BroadcastFromEncoder() +{ + mutex.lock(); + for (auto &client : clients) { + if (client.GetQueueSize() > 256 * 1024) { + g_debug("client is too slow, flushing its queue"); + client.CancelQueue(); + } + } + mutex.unlock(); + + Page *page; + while ((page = ReadPage()) != nullptr) { + BroadcastPage(page); + page->Unref(); + } +} + +inline bool +HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, GError **error_r) +{ + if (!encoder_write(encoder, chunk, size, error_r)) + return false; + + unflushed_input += size; + + BroadcastFromEncoder(); + return true; +} + +static size_t +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) +{ + HttpdOutput *httpd = Cast(ao); + + if (httpd->LockHasClients()) { + if (!httpd->EncodeAndPlay(chunk, size, error_r)) + return 0; + } + + if (!httpd->timer->started) + timer_start(httpd->timer); + timer_add(httpd->timer, size); + + return size; +} + +static bool +httpd_output_pause(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + if (httpd->LockHasClients()) { + static const char silence[1020] = { 0 }; + return httpd_output_play(ao, silence, sizeof(silence), + NULL) > 0; + } else { + return true; + } +} + +inline void +HttpdOutput::SendTag(const struct tag *tag) +{ + assert(tag != NULL); + + if (encoder->plugin->tag != NULL) { + /* embed encoder tags */ + + /* flush the current stream, and end it */ + + encoder_pre_tag(encoder, NULL); + BroadcastFromEncoder(); + + /* send the tag to the encoder - which starts a new + stream now */ + + encoder_tag(encoder, tag, NULL); + + /* the first page generated by the encoder will now be + used as the new "header" page, which is sent to all + new clients */ + + Page *page = ReadPage(); + if (page != NULL) { + if (header != NULL) + header->Unref(); + header = page; + BroadcastPage(page); + } + } else { + /* use Icy-Metadata */ + + if (metadata != NULL) + metadata->Unref(); + + static constexpr tag_type types[] = { + TAG_ALBUM, TAG_ARTIST, TAG_TITLE, + TAG_NUM_OF_ITEM_TYPES + }; + + metadata = icy_server_metadata_page(tag, &types[0]); + if (metadata != NULL) { + const ScopeLock protect(mutex); + for (auto &client : clients) + client.PushMetaData(metadata); + } + } +} + +static void +httpd_output_tag(struct audio_output *ao, const struct tag *tag) +{ + HttpdOutput *httpd = Cast(ao); + + httpd->SendTag(tag); +} + +static void +httpd_output_cancel(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.CancelQueue(); +} + +const struct audio_output_plugin httpd_output_plugin = { + "httpd", + nullptr, + httpd_output_init, + httpd_output_finish, + httpd_output_enable, + httpd_output_disable, + httpd_output_open, + httpd_output_close, + httpd_output_delay, + httpd_output_tag, + httpd_output_play, + nullptr, + httpd_output_cancel, + httpd_output_pause, + nullptr, +}; diff --git a/src/output/httpd_output_plugin.h b/src/output/HttpdOutputPlugin.hxx index d0eb1533f..c74d2bd4a 100644 --- a/src/output/httpd_output_plugin.h +++ b/src/output/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H +#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin httpd_output_plugin; diff --git a/src/output/null_output_plugin.c b/src/output/NullOutputPlugin.cxx index 9d7588fff..3596cbdcb 100644 --- a/src/output/null_output_plugin.c +++ b/src/output/NullOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "null_output_plugin.h" +#include "NullOutputPlugin.hxx" #include "output_api.h" #include "timer.h" @@ -26,7 +26,7 @@ #include <assert.h> -struct null_data { +struct NullOutput { struct audio_output base; bool sync; @@ -37,7 +37,7 @@ struct null_data { static struct audio_output * null_init(const struct config_param *param, GError **error_r) { - struct null_data *nd = g_new(struct null_data, 1); + NullOutput *nd = g_new(NullOutput, 1); if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { g_free(nd); @@ -52,7 +52,7 @@ null_init(const struct config_param *param, GError **error_r) static void null_finish(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; ao_base_finish(&nd->base); g_free(nd); @@ -62,7 +62,7 @@ static bool null_open(struct audio_output *ao, struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; if (nd->sync) nd->timer = timer_new(audio_format); @@ -73,7 +73,7 @@ null_open(struct audio_output *ao, struct audio_format *audio_format, static void null_close(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; if (nd->sync) timer_free(nd->timer); @@ -82,7 +82,7 @@ null_close(struct audio_output *ao) static unsigned null_delay(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; return nd->sync && nd->timer->started ? timer_delay(nd->timer) @@ -93,7 +93,7 @@ static size_t null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; struct timer *timer = nd->timer; if (!nd->sync) @@ -109,7 +109,7 @@ null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, static void null_cancel(struct audio_output *ao) { - struct null_data *nd = (struct null_data *)ao; + NullOutput *nd = (NullOutput *)ao; if (!nd->sync) return; @@ -118,12 +118,19 @@ null_cancel(struct audio_output *ao) } const struct audio_output_plugin null_output_plugin = { - .name = "null", - .init = null_init, - .finish = null_finish, - .open = null_open, - .close = null_close, - .delay = null_delay, - .play = null_play, - .cancel = null_cancel, + "null", + nullptr, + null_init, + null_finish, + nullptr, + nullptr, + null_open, + null_close, + null_delay, + nullptr, + null_play, + nullptr, + null_cancel, + nullptr, + nullptr, }; diff --git a/src/output/null_output_plugin.h b/src/output/NullOutputPlugin.hxx index 392bf0aa3..a58f1cb13 100644 --- a/src/output/null_output_plugin.h +++ b/src/output/NullOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_NULL_OUTPUT_PLUGIN_H -#define MPD_NULL_OUTPUT_PLUGIN_H +#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX +#define MPD_NULL_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin null_output_plugin; diff --git a/src/output/osx_output_plugin.c b/src/output/OSXOutputPlugin.cxx index cd51fca20..5a04fe1db 100644 --- a/src/output/osx_output_plugin.c +++ b/src/output/OSXOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,11 @@ */ #include "config.h" -#include "osx_output_plugin.h" +#include "OSXOutputPlugin.hxx" #include "output_api.h" -#include "fifo_buffer.h" +#include "util/fifo_buffer.h" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" #include <glib.h> #include <CoreAudio/AudioHardware.h> @@ -30,7 +32,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "osx" -struct osx_output { +struct OSXOutput { struct audio_output base; /* configuration settings */ @@ -39,8 +41,8 @@ struct osx_output { const char *device_name; AudioUnit au; - GMutex *mutex; - GCond *condition; + Mutex mutex; + Cond condition; struct fifo_buffer *buffer; }; @@ -63,7 +65,7 @@ osx_output_test_default_device(void) } static void -osx_output_configure(struct osx_output *oo, const struct config_param *param) +osx_output_configure(OSXOutput *oo, const struct config_param *param) { const char *device = config_get_block_string(param, "device", NULL); @@ -85,15 +87,13 @@ osx_output_configure(struct osx_output *oo, const struct config_param *param) static struct audio_output * osx_output_init(const struct config_param *param, GError **error_r) { - struct osx_output *oo = g_new(struct osx_output, 1); + OSXOutput *oo = new OSXOutput(); if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { - g_free(oo); + delete oo; return NULL; } osx_output_configure(oo, param); - oo->mutex = g_mutex_new(); - oo->condition = g_cond_new(); return &oo->base; } @@ -101,15 +101,13 @@ osx_output_init(const struct config_param *param, GError **error_r) static void osx_output_finish(struct audio_output *ao) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *oo = (OSXOutput *)ao; - g_mutex_free(od->mutex); - g_cond_free(od->condition); - g_free(od); + delete oo; } static bool -osx_output_set_device(struct osx_output *oo, GError **error) +osx_output_set_device(OSXOutput *oo, GError **error) { bool ret = true; OSStatus status; @@ -135,7 +133,7 @@ osx_output_set_device(struct osx_output *oo, GError **error) /* what are the available audio device IDs? */ numdevices = size / sizeof(AudioDeviceID); - deviceids = g_malloc(size); + deviceids = new AudioDeviceID[numdevices]; status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, deviceids); @@ -192,8 +190,7 @@ osx_output_set_device(struct osx_output *oo, GError **error) (unsigned int) deviceids[i], name); done: - if (deviceids != NULL) - g_free(deviceids); + delete[] deviceids; return ret; } @@ -205,13 +202,13 @@ osx_render(void *vdata, G_GNUC_UNUSED UInt32 in_number_frames, AudioBufferList *buffer_list) { - struct osx_output *od = (struct osx_output *) vdata; + OSXOutput *od = (OSXOutput *) vdata; AudioBuffer *buffer = &buffer_list->mBuffers[0]; size_t buffer_size = buffer->mDataByteSize; assert(od->buffer != NULL); - g_mutex_lock(od->mutex); + od->mutex.lock(); size_t nbytes; const void *src = fifo_buffer_read(od->buffer, &nbytes); @@ -225,8 +222,8 @@ osx_render(void *vdata, } else nbytes = 0; - g_cond_signal(od->condition); - g_mutex_unlock(od->mutex); + od->condition.signal(); + od->mutex.unlock(); buffer->mDataByteSize = nbytes; @@ -242,7 +239,7 @@ osx_render(void *vdata, static bool osx_output_enable(struct audio_output *ao, GError **error_r) { - struct osx_output *oo = (struct osx_output *)ao; + OSXOutput *oo = (OSXOutput *)ao; ComponentDescription desc; desc.componentType = kAudioUnitType_Output; @@ -293,7 +290,7 @@ osx_output_enable(struct audio_output *ao, GError **error_r) static void osx_output_disable(struct audio_output *ao) { - struct osx_output *oo = (struct osx_output *)ao; + OSXOutput *oo = (OSXOutput *)ao; CloseComponent(oo->au); } @@ -301,17 +298,16 @@ osx_output_disable(struct audio_output *ao) static void osx_output_cancel(struct audio_output *ao) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; - g_mutex_lock(od->mutex); + const ScopeLock protect(od->mutex); fifo_buffer_clear(od->buffer); - g_mutex_unlock(od->mutex); } static void osx_output_close(struct audio_output *ao) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; AudioOutputUnitStop(od->au); AudioUnitUninitialize(od->au); @@ -322,7 +318,7 @@ osx_output_close(struct audio_output *ao) static bool osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; AudioStreamBasicDescription stream_description; stream_description.mSampleRate = audio_format->sample_rate; @@ -397,9 +393,9 @@ static size_t osx_output_play(struct audio_output *ao, const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; - g_mutex_lock(od->mutex); + const ScopeLock protect(od->mutex); void *dest; size_t max_length; @@ -410,7 +406,7 @@ osx_output_play(struct audio_output *ao, const void *chunk, size_t size, break; /* wait for some free space in the buffer */ - g_cond_wait(od->condition, od->mutex); + od->condition.wait(od->mutex); } if (size > max_length) @@ -419,20 +415,23 @@ osx_output_play(struct audio_output *ao, const void *chunk, size_t size, memcpy(dest, chunk, size); fifo_buffer_append(od->buffer, size); - g_mutex_unlock(od->mutex); - return size; } const struct audio_output_plugin osx_output_plugin = { - .name = "osx", - .test_default_device = osx_output_test_default_device, - .init = osx_output_init, - .finish = osx_output_finish, - .enable = osx_output_enable, - .disable = osx_output_disable, - .open = osx_output_open, - .close = osx_output_close, - .play = osx_output_play, - .cancel = osx_output_cancel, + "osx", + osx_output_test_default_device, + osx_output_init, + osx_output_finish, + osx_output_enable, + osx_output_disable, + osx_output_open, + osx_output_close, + nullptr, + nullptr, + osx_output_play, + nullptr, + osx_output_cancel, + nullptr, + nullptr, }; diff --git a/src/output/osx_output_plugin.h b/src/output/OSXOutputPlugin.hxx index 814702d4f..2a4172880 100644 --- a/src/output/osx_output_plugin.h +++ b/src/output/OSXOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OSX_OUTPUT_PLUGIN_H -#define MPD_OSX_OUTPUT_PLUGIN_H +#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX +#define MPD_OSX_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin osx_output_plugin; diff --git a/src/output/oss_output_plugin.c b/src/output/OssOutputPlugin.cxx index e366a4537..0111b13f6 100644 --- a/src/output/oss_output_plugin.c +++ b/src/output/OssOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,10 @@ */ #include "config.h" -#include "oss_output_plugin.h" +#include "OssOutputPlugin.hxx" #include "output_api.h" -#include "mixer_list.h" +#include "MixerList.hxx" #include "fd_util.h" -#include "glib_compat.h" #include <glib.h> @@ -60,7 +59,7 @@ struct oss_data { struct audio_output base; #ifdef AFMT_S24_PACKED - struct pcm_export_state export; + struct pcm_export_state pcm_export; #endif int fd; @@ -163,11 +162,10 @@ oss_output_test_default_device(void) static struct audio_output * oss_open_default(GError **error) { - int i; int err[G_N_ELEMENTS(default_devices)]; enum oss_stat ret[G_N_ELEMENTS(default_devices)]; - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) { ret[i] = oss_stat_device(default_devices[i], &err[i]); if (ret[i] == OSS_STAT_NO_ERROR) { struct oss_data *od = oss_data_new(); @@ -182,7 +180,7 @@ oss_open_default(GError **error) } } - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + for (int i = G_N_ELEMENTS(default_devices); --i >= 0; ) { const char *dev = default_devices[i]; switch(ret[i]) { case OSS_STAT_NO_ERROR: @@ -243,7 +241,7 @@ oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) { struct oss_data *od = (struct oss_data *)ao; - pcm_export_init(&od->export); + pcm_export_init(&od->pcm_export); return true; } @@ -252,7 +250,7 @@ oss_output_disable(struct audio_output *ao) { struct oss_data *od = (struct oss_data *)ao; - pcm_export_deinit(&od->export); + pcm_export_deinit(&od->pcm_export); } #endif @@ -504,7 +502,7 @@ oss_probe_sample_format(int fd, enum sample_format sample_format, enum sample_format *sample_format_r, int *oss_format_r, #ifdef AFMT_S24_PACKED - struct pcm_export_state *export, + struct pcm_export_state *pcm_export, #endif GError **error_r) { @@ -539,7 +537,7 @@ oss_probe_sample_format(int fd, enum sample_format sample_format, *oss_format_r = oss_format; #ifdef AFMT_S24_PACKED - pcm_export_open(export, sample_format, 0, false, false, + pcm_export_open(pcm_export, sample_format, 0, false, false, oss_format == AFMT_S24_PACKED, oss_format == AFMT_S24_PACKED && G_BYTE_ORDER != G_LITTLE_ENDIAN); @@ -556,16 +554,16 @@ static bool oss_setup_sample_format(int fd, struct audio_format *audio_format, int *oss_format_r, #ifdef AFMT_S24_PACKED - struct pcm_export_state *export, + struct pcm_export_state *pcm_export, #endif GError **error_r) { enum sample_format mpd_format; enum oss_setup_result result = - oss_probe_sample_format(fd, audio_format->format, + oss_probe_sample_format(fd, sample_format(audio_format->format), &mpd_format, oss_format_r, #ifdef AFMT_S24_PACKED - export, + pcm_export, #endif error_r); switch (result) { @@ -603,7 +601,7 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format, result = oss_probe_sample_format(fd, mpd_format, &mpd_format, oss_format_r, #ifdef AFMT_S24_PACKED - export, + pcm_export, #endif error_r); switch (result) { @@ -635,7 +633,7 @@ oss_setup(struct oss_data *od, struct audio_format *audio_format, oss_setup_sample_rate(od->fd, audio_format, error_r) && oss_setup_sample_format(od->fd, audio_format, &od->oss_format, #ifdef AFMT_S24_PACKED - &od->export, + &od->pcm_export, #endif error_r); } @@ -749,14 +747,14 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size, return 0; #ifdef AFMT_S24_PACKED - chunk = pcm_export(&od->export, chunk, size, &size); + chunk = pcm_export(&od->pcm_export, chunk, size, &size); #endif while (true) { ret = write(od->fd, chunk, size); if (ret > 0) { #ifdef AFMT_S24_PACKED - ret = pcm_export_source_size(&od->export, ret); + ret = pcm_export_source_size(&od->pcm_export, ret); #endif return ret; } @@ -771,18 +769,25 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size, } const struct audio_output_plugin oss_output_plugin = { - .name = "oss", - .test_default_device = oss_output_test_default_device, - .init = oss_output_init, - .finish = oss_output_finish, + "oss", + oss_output_test_default_device, + oss_output_init, + oss_output_finish, #ifdef AFMT_S24_PACKED - .enable = oss_output_enable, - .disable = oss_output_disable, + oss_output_enable, + oss_output_disable, +#else + nullptr, + nullptr, #endif - .open = oss_output_open, - .close = oss_output_close, - .play = oss_output_play, - .cancel = oss_output_cancel, - - .mixer_plugin = &oss_mixer_plugin, + oss_output_open, + oss_output_close, + nullptr, + nullptr, + oss_output_play, + nullptr, + oss_output_cancel, + nullptr, + + &oss_mixer_plugin, }; diff --git a/src/output/oss_output_plugin.h b/src/output/OssOutputPlugin.hxx index 2aecc2b3a..6c5c9530b 100644 --- a/src/output/oss_output_plugin.h +++ b/src/output/OssOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_OSS_OUTPUT_PLUGIN_H -#define MPD_OSS_OUTPUT_PLUGIN_H +#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX +#define MPD_OSS_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin oss_output_plugin; diff --git a/src/output/roar_output_plugin.c b/src/output/RoarOutputPlugin.cxx index 1c2c48321..9d6c45917 100644 --- a/src/output/roar_output_plugin.c +++ b/src/output/RoarOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen * @@ -19,25 +19,19 @@ */ #include "config.h" -#include "roar_output_plugin.h" +#include "RoarOutputPlugin.hxx" #include "output_api.h" -#include "mixer_list.h" -#include "roar_output_plugin.h" +#include "MixerList.hxx" +#include "thread/Mutex.hxx" #include <glib.h> -#include <stdint.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> #include <roaraudio.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "roaraudio" -typedef struct roar -{ +struct RoarOutput { struct audio_output base; roar_vs_t * vss; @@ -47,9 +41,18 @@ typedef struct roar int role; struct roar_connection con; struct roar_audio_info info; - GMutex *lock; + Mutex mutex; volatile bool alive; -} roar_t; + + RoarOutput() + :err(ROAR_ERROR_NONE), + host(nullptr), name(nullptr) {} + + ~RoarOutput() { + g_free(host); + g_free(name); + } +}; static inline GQuark roar_output_quark(void) @@ -58,9 +61,9 @@ roar_output_quark(void) } static int -roar_output_get_volume_locked(struct roar *roar) +roar_output_get_volume_locked(RoarOutput *roar) { - if (roar->vss == NULL || !roar->alive) + if (roar->vss == nullptr || !roar->alive) return -1; float l, r; @@ -72,20 +75,18 @@ roar_output_get_volume_locked(struct roar *roar) } int -roar_output_get_volume(struct roar *roar) +roar_output_get_volume(RoarOutput *roar) { - g_mutex_lock(roar->lock); - int volume = roar_output_get_volume_locked(roar); - g_mutex_unlock(roar->lock); - return volume; + const ScopeLock protect(roar->mutex); + return roar_output_get_volume_locked(roar); } static bool -roar_output_set_volume_locked(struct roar *roar, unsigned volume) +roar_output_set_volume_locked(RoarOutput *roar, unsigned volume) { assert(volume <= 100); - if (roar->vss == NULL || !roar->alive) + if (roar->vss == nullptr || !roar->alive) return false; int error; @@ -96,22 +97,20 @@ roar_output_set_volume_locked(struct roar *roar, unsigned volume) } bool -roar_output_set_volume(struct roar *roar, unsigned volume) +roar_output_set_volume(RoarOutput *roar, unsigned volume) { - g_mutex_lock(roar->lock); - bool success = roar_output_set_volume_locked(roar, volume); - g_mutex_unlock(roar->lock); - return success; + const ScopeLock protect(roar->mutex); + return roar_output_set_volume_locked(roar, volume); } static void -roar_configure(struct roar * self, const struct config_param *param) +roar_configure(RoarOutput *self, const struct config_param *param) { - self->host = config_dup_block_string(param, "server", NULL); + self->host = config_dup_block_string(param, "server", nullptr); self->name = config_dup_block_string(param, "name", "MPD"); const char *role = config_get_block_string(param, "role", "music"); - self->role = role != NULL + self->role = role != nullptr ? roar_str2role(role) : ROAR_ROLE_MUSIC; } @@ -119,15 +118,13 @@ roar_configure(struct roar * self, const struct config_param *param) static struct audio_output * roar_init(const struct config_param *param, GError **error_r) { - struct roar *self = g_new0(struct roar, 1); + RoarOutput *self = new RoarOutput(); if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) { - g_free(self); - return NULL; + delete self; + return nullptr; } - self->lock = g_mutex_new(); - self->err = ROAR_ERROR_NONE; roar_configure(self, param); return &self->base; } @@ -135,14 +132,10 @@ roar_init(const struct config_param *param, GError **error_r) static void roar_finish(struct audio_output *ao) { - struct roar *self = (struct roar *)ao; - - g_free(self->host); - g_free(self->name); - g_mutex_free(self->lock); + RoarOutput *self = (RoarOutput *)ao; ao_base_finish(&self->base); - g_free(self); + delete self; } static void @@ -181,24 +174,22 @@ roar_use_audio_format(struct roar_audio_info *info, static bool roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); 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) + if (self->vss == nullptr || 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; } @@ -208,43 +199,41 @@ roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **e &(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(struct audio_output *ao) { - struct roar *self = (struct roar *)ao; - g_mutex_lock(self->lock); + RoarOutput *self = (RoarOutput *)ao; + const ScopeLock protect(self->mutex); + self->alive = false; - if (self->vss != NULL) + if (self->vss != nullptr) roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); - self->vss = NULL; + self->vss = nullptr; roar_disconnect(&(self->con)); - g_mutex_unlock(self->lock); } static void -roar_cancel_locked(struct roar *self) +roar_cancel_locked(RoarOutput *self) { - if (self->vss == NULL) + if (self->vss == nullptr) return; roar_vs_t *vss = self->vss; - self->vss = NULL; + self->vss = nullptr; 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) + if (vss == nullptr) return; if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, @@ -262,20 +251,19 @@ roar_cancel_locked(struct roar *self) static void roar_cancel(struct audio_output *ao) { - struct roar *self = (struct roar *)ao; + RoarOutput *self = (RoarOutput *)ao; - g_mutex_lock(self->lock); + const ScopeLock protect(self->mutex); roar_cancel_locked(self); - g_mutex_unlock(self->lock); } static size_t roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct roar *self = (struct roar *)ao; + RoarOutput *self = (RoarOutput *)ao; ssize_t rc; - if (self->vss == NULL) + if (self->vss == nullptr) { g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); return 0; @@ -332,19 +320,20 @@ roar_tag_convert(enum tag_type type, bool *is_uuid) return "HASH"; default: - return NULL; + return nullptr; } } static void roar_send_tag(struct audio_output *ao, const struct tag *meta) { - struct roar *self = (struct roar *)ao; + RoarOutput *self = (RoarOutput *)ao; - if (self->vss == NULL) + if (self->vss == nullptr) return; - g_mutex_lock(self->lock); + const ScopeLock protect(self->mutex); + size_t cnt = 1; struct roar_keyval vals[32]; memset(vals, 0, sizeof(vals)); @@ -361,7 +350,7 @@ roar_send_tag(struct audio_output *ao, const struct tag *meta) { bool is_uuid = false; const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); - if (key != NULL) + if (key != nullptr) { if (is_uuid) { @@ -383,19 +372,22 @@ roar_send_tag(struct audio_output *ao, const struct tag *meta) 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 + "roar", + nullptr, + roar_init, + roar_finish, + nullptr, + nullptr, + roar_open, + roar_close, + nullptr, + roar_send_tag, + roar_play, + nullptr, + roar_cancel, + nullptr, + &roar_mixer_plugin, }; diff --git a/src/output/roar_output_plugin.h b/src/output/RoarOutputPlugin.hxx index 78b628cc4..faa4b4d5c 100644 --- a/src/output/roar_output_plugin.h +++ b/src/output/RoarOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,16 +20,14 @@ #ifndef MPD_ROAR_OUTPUT_PLUGIN_H #define MPD_ROAR_OUTPUT_PLUGIN_H -#include <stdbool.h> - -struct roar; +struct RoarOutput; extern const struct audio_output_plugin roar_output_plugin; int -roar_output_get_volume(struct roar *roar); +roar_output_get_volume(RoarOutput *roar); bool -roar_output_set_volume(struct roar *roar, unsigned volume); +roar_output_set_volume(RoarOutput *roar, unsigned volume); #endif diff --git a/src/output/winmm_output_plugin.c b/src/output/WinmmOutputPlugin.cxx index 4d95834b9..b9652fc0b 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/WinmmOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,26 +18,24 @@ */ #include "config.h" -#include "winmm_output_plugin.h" +#include "WinmmOutputPlugin.hxx" #include "output_api.h" #include "pcm_buffer.h" -#include "mixer_list.h" -#include "winmm_output_plugin.h" +#include "MixerList.hxx" #include <stdlib.h> #include <string.h> -#include <windows.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "winmm_output" -struct winmm_buffer { +struct WinmmBuffer { struct pcm_buffer buffer; WAVEHDR hdr; }; -struct winmm_output { +struct WinmmOutput { struct audio_output base; UINT device_id; @@ -49,7 +47,7 @@ struct winmm_output { */ HANDLE event; - struct winmm_buffer buffers[8]; + WinmmBuffer buffers[8]; unsigned next_buffer; }; @@ -63,7 +61,7 @@ winmm_output_quark(void) } HWAVEOUT -winmm_output_get_handle(struct winmm_output* output) +winmm_output_get_handle(WinmmOutput *output) { return output->handle; } @@ -78,7 +76,7 @@ static bool get_device_id(const char *device_name, UINT *device_id, GError **error_r) { /* if device is not specified use wave mapper */ - if (device_name == NULL) { + if (device_name == nullptr) { *device_id = WAVE_MAPPER; return true; } @@ -118,17 +116,17 @@ fail: static struct audio_output * winmm_output_init(const struct config_param *param, GError **error_r) { - struct winmm_output *wo = g_new(struct winmm_output, 1); + WinmmOutput *wo = new WinmmOutput(); if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) { g_free(wo); - return NULL; + return nullptr; } - const char *device = config_get_block_string(param, "device", NULL); + const char *device = config_get_block_string(param, "device", nullptr); if (!get_device_id(device, &wo->device_id, error_r)) { ao_base_finish(&wo->base); g_free(wo); - return NULL; + return nullptr; } return &wo->base; @@ -137,20 +135,20 @@ winmm_output_init(const struct config_param *param, GError **error_r) static void winmm_output_finish(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; ao_base_finish(&wo->base); - g_free(wo); + delete wo; } static bool winmm_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; - wo->event = CreateEvent(NULL, false, false, NULL); - if (wo->event == NULL) { + wo->event = CreateEvent(nullptr, false, false, nullptr); + if (wo->event == nullptr) { g_set_error(error_r, winmm_output_quark(), 0, "CreateEvent() failed"); return false; @@ -204,7 +202,7 @@ winmm_output_open(struct audio_output *ao, struct audio_format *audio_format, static void winmm_output_close(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) pcm_buffer_deinit(&wo->buffers[i].buffer); @@ -218,17 +216,17 @@ winmm_output_close(struct audio_output *ao) * Copy data into a buffer, and prepare the wave header. */ static bool -winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, +winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, const void *data, size_t size, GError **error_r) { void *dest = pcm_buffer_get(&buffer->buffer, size); - assert(dest != NULL); + assert(dest != nullptr); memcpy(dest, data, size); memset(&buffer->hdr, 0, sizeof(buffer->hdr)); - buffer->hdr.lpData = dest; + buffer->hdr.lpData = (LPSTR)dest; buffer->hdr.dwBufferLength = size; MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, @@ -246,7 +244,7 @@ winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, * Wait until the buffer is finished. */ static bool -winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, +winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, GError **error_r) { if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) @@ -273,10 +271,10 @@ winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, static size_t winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; /* get the next buffer from the ring and prepare it */ - struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; + WinmmBuffer *buffer = &wo->buffers[wo->next_buffer]; if (!winmm_drain_buffer(wo, buffer, error_r) || !winmm_set_buffer(wo, buffer, chunk, size, error_r)) return 0; @@ -300,7 +298,7 @@ winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GErro } static bool -winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r) +winmm_drain_all_buffers(WinmmOutput *wo, GError **error_r) { for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i) if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) @@ -314,12 +312,12 @@ winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r) } static void -winmm_stop(struct winmm_output *wo) +winmm_stop(WinmmOutput *wo) { waveOutReset(wo->handle); for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { - struct winmm_buffer *buffer = &wo->buffers[i]; + WinmmBuffer *buffer = &wo->buffers[i]; waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); } @@ -328,29 +326,34 @@ winmm_stop(struct winmm_output *wo) static void winmm_output_drain(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; - if (!winmm_drain_all_buffers(wo, NULL)) + if (!winmm_drain_all_buffers(wo, nullptr)) winmm_stop(wo); } static void winmm_output_cancel(struct audio_output *ao) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; winmm_stop(wo); } const struct audio_output_plugin winmm_output_plugin = { - .name = "winmm", - .test_default_device = winmm_output_test_default_device, - .init = winmm_output_init, - .finish = winmm_output_finish, - .open = winmm_output_open, - .close = winmm_output_close, - .play = winmm_output_play, - .drain = winmm_output_drain, - .cancel = winmm_output_cancel, - .mixer_plugin = &winmm_mixer_plugin, + "winmm", + winmm_output_test_default_device, + winmm_output_init, + winmm_output_finish, + nullptr, + nullptr, + winmm_output_open, + winmm_output_close, + nullptr, + nullptr, + winmm_output_play, + winmm_output_drain, + winmm_output_cancel, + nullptr, + &winmm_mixer_plugin, }; diff --git a/src/output/winmm_output_plugin.h b/src/output/WinmmOutputPlugin.hxx index 0605530e1..e8688782e 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/WinmmOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,25 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_WINMM_OUTPUT_PLUGIN_H -#define MPD_WINMM_OUTPUT_PLUGIN_H +#ifndef MPD_WINMM_OUTPUT_PLUGIN_HXX +#define MPD_WINMM_OUTPUT_PLUGIN_HXX #include "check.h" #ifdef ENABLE_WINMM_OUTPUT +#include "gcc.h" + #include <windows.h> +#include <mmsystem.h> -struct winmm_output; +struct WinmmOutput; extern const struct audio_output_plugin winmm_output_plugin; -HWAVEOUT winmm_output_get_handle(struct winmm_output*); +gcc_pure +HWAVEOUT +winmm_output_get_handle(WinmmOutput *); #endif diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index 022be0b4a..3d6171ae2 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -20,7 +20,6 @@ #include "config.h" #include "fifo_output_plugin.h" #include "output_api.h" -#include "utils.h" #include "timer.h" #include "fd_util.h" #include "open.h" diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c deleted file mode 100644 index 72de90457..000000000 --- a/src/output/httpd_client.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * 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 "httpd_client.h" -#include "httpd_internal.h" -#include "fifo_buffer.h" -#include "page.h" -#include "icy_server.h" -#include "glib_socket.h" - -#include <stdbool.h> -#include <assert.h> -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -struct httpd_client { - /** - * The httpd output object this client is connected to. - */ - struct httpd_output *httpd; - - /** - * The TCP socket. - */ - GIOChannel *channel; - - /** - * The GLib main loop source id for reading from the socket, - * and to detect errors. - */ - guint read_source_id; - - /** - * The GLib main loop source id for writing to the socket. If - * 0, then there is no event source currently (because there - * are no queued pages). - */ - guint write_source_id; - - /** - * For buffered reading. This pointer is only valid while the - * HTTP request is read. - */ - struct fifo_buffer *input; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #page objects to be sent to the client. - */ - GQueue *pages; - - /** - * The #page which is currently being sent to the client. - */ - struct page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - guint metaint; - - /** - * The metadata as #page which is currently being sent to the client. - */ - struct page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - guint metadata_fill; -}; - -static void -httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct page *page = data; - - page_unref(page); -} - -void -httpd_client_free(struct httpd_client *client) -{ - assert(client != NULL); - - if (client->state == RESPONSE) { - if (client->write_source_id != 0) - g_source_remove(client->write_source_id); - - if (client->current_page != NULL) - page_unref(client->current_page); - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_free(client->pages); - } else - fifo_buffer_free(client->input); - - if (client->metadata) - page_unref (client->metadata); - - g_source_remove(client->read_source_id); - g_io_channel_unref(client->channel); - g_free(client); -} - -/** - * Frees the client and removes it from the server's client list. - */ -static void -httpd_client_close(struct httpd_client *client) -{ - assert(client != NULL); - - httpd_output_remove_client(client->httpd, client); - httpd_client_free(client); -} - -/** - * Switch the client to the "RESPONSE" state. - */ -static void -httpd_client_begin_response(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - client->state = RESPONSE; - client->write_source_id = 0; - client->pages = g_queue_new(); - client->current_page = NULL; - - httpd_output_send_header(client->httpd, client); -} - -/** - * Handle a line of the HTTP request. - */ -static bool -httpd_client_handle_line(struct httpd_client *client, const char *line) -{ - assert(client->state != RESPONSE); - - if (client->state == REQUEST) { - if (strncmp(line, "GET /", 5) != 0) { - /* only GET is supported */ - g_warning("malformed request line from client"); - return false; - } - - line = strchr(line + 5, ' '); - if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - httpd_client_begin_response(client); - return true; - } - - /* after the request line, request headers follow */ - client->state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - httpd_client_begin_response(client); - return true; - } - - if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) { - /* Send icy metadata */ - client->metadata_requested = - client->metadata_supported; - return true; - } - - if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { - /* Send as dlna */ - client->dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - client->metadata_supported = false; - client->metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Check if a complete line of input is present in the input buffer, - * and duplicates it. It is removed from the input buffer. The - * return value has to be freed with g_free(). - */ -static char * -httpd_client_read_line(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - /* empty input buffer */ - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - /* incomplete line */ - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - /* remove trailing whitespace (e.g. '\r') */ - return g_strchomp(line); -} - -/** - * Sends the status line and response headers to the client. - */ -static bool -httpd_client_send_response(struct httpd_client *client) -{ - char buffer[1024]; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->state == RESPONSE); - - if (client->dlna_streaming_requested) { - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - client->httpd->content_type); - - } else if (client->metadata_requested) { - gchar *metadata_header; - - metadata_header = icy_server_metadata_header( - client->httpd->name, - client->httpd->genre, - client->httpd->website, - client->httpd->content_type, - client->metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - g_free(metadata_header); - - } else { /* revert to a normal HTTP request */ - g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - client->httpd->content_type); - } - - status = g_io_channel_write_chars(client->channel, - buffer, strlen(buffer), - &bytes_written, &error); - - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - return false; - } - - /* unreachable */ - httpd_client_close(client); - return false; -} - -/** - * Data has been received from the client and it is appended to the - * input buffer. - */ -static bool -httpd_client_received(struct httpd_client *client) -{ - assert(client != NULL); - assert(client->state != RESPONSE); - - char *line; - bool success; - - while ((line = httpd_client_read_line(client)) != NULL) { - success = httpd_client_handle_line(client, line); - g_free(line); - if (!success) { - assert(client->state != RESPONSE); - return false; - } - - if (client->state == RESPONSE) { - if (!fifo_buffer_is_empty(client->input)) { - g_warning("unexpected input from client"); - return false; - } - - fifo_buffer_free(client->input); - - return httpd_client_send_response(client); - } - } - - return true; -} - -static bool -httpd_client_read(struct httpd_client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - if (client->state == RESPONSE) { - /* the client has already sent the request, and he - must not send more */ - char buffer[1]; - - status = g_io_channel_read_chars(client->channel, buffer, - sizeof(buffer), &bytes_read, - NULL); - if (status == G_IO_STATUS_NORMAL) - g_warning("unexpected input from client"); - - return false; - } - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("buffer overflow"); - return false; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - fifo_buffer_append(client->input, bytes_read); - return httpd_client_received(client); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return true; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client: %s", - error->message); - g_error_free(error); - return false; - } - - /* unreachable */ - return false; -} - -static gboolean -httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - bool ret; - - g_mutex_lock(httpd->mutex); - - if (condition == G_IO_IN && httpd_client_read(client)) { - ret = true; - } else { - httpd_client_close(client); - ret = false; - } - - g_mutex_unlock(httpd->mutex); - - return ret; -} - -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) -{ - struct httpd_client *client = g_new(struct httpd_client, 1); - - client->httpd = httpd; - - client->channel = g_io_channel_new_socket(fd); - - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->read_source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - httpd_client_in_event, client); - - client->input = fifo_buffer_new(4096); - client->state = REQUEST; - - client->dlna_streaming_requested = false; - client->metadata_supported = metadata_supported; - client->metadata_requested = false; - client->metadata_sent = true; - client->metaint = 8192; /*TODO: just a std value */ - client->metadata = NULL; - client->metadata_current_position = 0; - client->metadata_fill = 0; - - return client; -} - -static void -httpd_client_add_page_size(gpointer data, gpointer user_data) -{ - struct page *page = data; - size_t *size = user_data; - - *size += page->size; -} - -size_t -httpd_client_queue_size(const struct httpd_client *client) -{ - size_t size = 0; - - if (client->state != RESPONSE) - return 0; - - g_queue_foreach(client->pages, httpd_client_add_page_size, &size); - return size; -} - -void -httpd_client_cancel(struct httpd_client *client) -{ - if (client->state != RESPONSE) - return; - - g_queue_foreach(client->pages, httpd_client_unref_page, NULL); - g_queue_clear(client->pages); - - if (client->write_source_id != 0 && client->current_page == NULL) { - g_source_remove(client->write_source_id); - client->write_source_id = 0; - } -} - -static GIOStatus -write_page_to_channel(GIOChannel *channel, - const struct page *page, size_t position, - gsize *bytes_written_r, GError **error) -{ - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - return g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - page->size - position, - bytes_written_r, error); -} - -static GIOStatus -write_n_bytes_to_channel(GIOChannel *channel, const struct page *page, - size_t position, gint n, - gsize *bytes_written_r, GError **error) -{ - GIOStatus status; - - assert(channel != NULL); - assert(page != NULL); - assert(position < page->size); - - if (n == -1) { - status = write_page_to_channel (channel, page, position, - bytes_written_r, error); - } else { - status = g_io_channel_write_chars(channel, - (const gchar*)page->data + position, - n, bytes_written_r, error); - } - - return status; -} - -static gint -bytes_left_till_metadata (struct httpd_client *client) -{ - assert(client != NULL); - - if (client->metadata_requested && - client->current_page->size - client->current_position - > client->metaint - client->metadata_fill) - return client->metaint - client->metadata_fill; - - return -1; -} - -static gboolean -httpd_client_out_event(GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) -{ - struct httpd_client *client = data; - struct httpd_output *httpd = client->httpd; - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - gint bytes_to_write; - - g_mutex_lock(httpd->mutex); - - assert(condition == G_IO_OUT); - assert(client->state == RESPONSE); - - if (client->write_source_id == 0) { - /* another thread has removed the event source while - this thread was waiting for httpd->mutex */ - g_mutex_unlock(httpd->mutex); - return false; - } - - if (client->current_page == NULL) { - client->current_page = g_queue_pop_head(client->pages); - client->current_position = 0; - } - - bytes_to_write = bytes_left_till_metadata(client); - - if (bytes_to_write == 0) { - gint metadata_to_write; - - metadata_to_write = client->metadata_current_position; - - if (!client->metadata_sent) { - status = write_page_to_channel(source, - client->metadata, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (client->metadata->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - client->metadata_sent = true; - } - } else { - struct page *empty_meta; - guchar empty_data = 0; - - empty_meta = page_new_copy(&empty_data, 1); - - status = write_page_to_channel(source, - empty_meta, - metadata_to_write, - &bytes_written, &error); - - client->metadata_current_position += bytes_written; - - if (empty_meta->size - - client->metadata_current_position == 0) { - client->metadata_fill = 0; - client->metadata_current_position = 0; - } - } - - bytes_written = 0; - } else { - status = write_n_bytes_to_channel(source, client->current_page, - client->current_position, bytes_to_write, - &bytes_written, &error); - } - - switch (status) { - case G_IO_STATUS_NORMAL: - client->current_position += bytes_written; - assert(client->current_position <= client->current_page->size); - - if (client->metadata_requested) - client->metadata_fill += bytes_written; - - if (client->current_position >= client->current_page->size) { - page_unref(client->current_page); - client->current_page = NULL; - - if (g_queue_is_empty(client->pages)) { - /* all pages are sent: remove the - event source */ - client->write_source_id = 0; - - g_mutex_unlock(httpd->mutex); - return false; - } - } - - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_AGAIN: - g_mutex_unlock(httpd->mutex); - return true; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - g_warning("failed to write to client: %s", error->message); - g_error_free(error); - - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; - } - - /* unreachable */ - httpd_client_close(client); - g_mutex_unlock(httpd->mutex); - return false; -} - -void -httpd_client_send(struct httpd_client *client, struct page *page) -{ - if (client->state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page_ref(page); - g_queue_push_tail(client->pages, page); - - if (client->write_source_id == 0) - client->write_source_id = - g_io_add_watch(client->channel, G_IO_OUT, - httpd_client_out_event, client); -} - -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page) -{ - if (client->metadata) { - page_unref(client->metadata); - client->metadata = NULL; - } - - g_return_if_fail (page); - - page_ref(page); - client->metadata = page; - client->metadata_sent = false; -} diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h deleted file mode 100644 index 739163f42..000000000 --- a/src/output/httpd_client.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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_HTTPD_CLIENT_H -#define MPD_OUTPUT_HTTPD_CLIENT_H - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; -struct httpd_output; -struct page; - -/** - * Creates a new #httpd_client object - * - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ -struct httpd_client * -httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported); - -/** - * Frees memory and resources allocated by the #httpd_client object. - * This does not remove it from the #httpd_output object. - */ -void -httpd_client_free(struct httpd_client *client); - -/** - * Returns the total size of this client's page queue. - */ -size_t -httpd_client_queue_size(const struct httpd_client *client); - -/** - * Clears the page queue. - */ -void -httpd_client_cancel(struct httpd_client *client); - -/** - * Appends a page to the client's queue. - */ -void -httpd_client_send(struct httpd_client *client, struct page *page); - -/** - * Sends the passed metadata. - */ -void -httpd_client_send_metadata(struct httpd_client *client, struct page *page); - -#endif diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c deleted file mode 100644 index 1d730df7f..000000000 --- a/src/output/httpd_output_plugin.c +++ /dev/null @@ -1,623 +0,0 @@ -/* - * 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 "httpd_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "resolver.h" -#include "page.h" -#include "icy_server.h" -#include "fd_util.h" -#include "server_socket.h" - -#include <assert.h> - -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> - -#ifdef HAVE_LIBWRAP -#include <sys/socket.h> /* needed for AF_UNIX */ -#include <tcpd.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -/** - * The quark used for GError.domain. - */ -static inline GQuark -httpd_output_quark(void) -{ - return g_quark_from_static_string("httpd_output"); -} - -/** - * Check whether there is at least one client. - * - * Caller must lock the mutex. - */ -G_GNUC_PURE -static bool -httpd_output_has_clients(const struct httpd_output *httpd) -{ - return httpd->clients != NULL; -} - -/** - * Check whether there is at least one client. - */ -G_GNUC_PURE -static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) -{ - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; -} - -static void -httpd_listen_in_event(int fd, const struct sockaddr *address, - size_t address_length, int uid, void *ctx); - -static bool -httpd_output_bind(struct httpd_output *httpd, GError **error_r) -{ - httpd->open = false; - - g_mutex_lock(httpd->mutex); - bool success = server_socket_open(httpd->server_socket, error_r); - g_mutex_unlock(httpd->mutex); - - return success; -} - -static void -httpd_output_unbind(struct httpd_output *httpd) -{ - assert(!httpd->open); - - g_mutex_lock(httpd->mutex); - server_socket_close(httpd->server_socket); - g_mutex_unlock(httpd->mutex); -} - -static struct audio_output * -httpd_output_init(const struct config_param *param, - GError **error) -{ - struct httpd_output *httpd = g_new(struct httpd_output, 1); - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { - g_free(httpd); - return NULL; - } - - /* read configuration */ - httpd->name = - config_get_block_string(param, "name", "Set name in config"); - httpd->genre = - config_get_block_string(param, "genre", "Set genre in config"); - httpd->website = - config_get_block_string(param, "website", "Set website in config"); - - guint port = config_get_block_unsigned(param, "port", 8000); - - const char *encoder_name = - config_get_block_string(param, "encoder", "vorbis"); - const struct encoder_plugin *encoder_plugin = - encoder_plugin_get(encoder_name); - if (encoder_plugin == NULL) { - g_set_error(error, httpd_output_quark(), 0, - "No such encoder: %s", encoder_name); - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); - - /* set up bind_to_address */ - - httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); - - const char *bind_to_address = - config_get_block_string(param, "bind_to_address", NULL); - bool success = bind_to_address != NULL && - strcmp(bind_to_address, "any") != 0 - ? server_socket_add_host(httpd->server_socket, bind_to_address, - port, error) - : server_socket_add_port(httpd->server_socket, port, error); - if (!success) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* initialize metadata */ - httpd->metadata = NULL; - httpd->unflushed_input = 0; - - /* initialize encoder */ - - httpd->encoder = encoder_init(encoder_plugin, param, error); - if (httpd->encoder == NULL) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* determine content type */ - httpd->content_type = encoder_get_mime_type(httpd->encoder); - if (httpd->content_type == NULL) { - httpd->content_type = "application/octet-stream"; - } - - httpd->mutex = g_mutex_new(); - - return &httpd->base; -} - -static void -httpd_output_finish(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd->metadata) - page_unref(httpd->metadata); - - encoder_finish(httpd->encoder); - server_socket_free(httpd->server_socket); - g_mutex_free(httpd->mutex); - ao_base_finish(&httpd->base); - g_free(httpd); -} - -/** - * Creates a new #httpd_client object and adds it into the - * httpd_output.clients linked list. - */ -static void -httpd_client_add(struct httpd_output *httpd, int fd) -{ - struct httpd_client *client = - httpd_client_new(httpd, fd, - httpd->encoder->plugin->tag == NULL); - - httpd->clients = g_list_prepend(httpd->clients, client); - httpd->clients_cnt++; - - /* pass metadata to client */ - if (httpd->metadata) - httpd_client_send_metadata(client, httpd->metadata); -} - -static void -httpd_listen_in_event(int fd, const struct sockaddr *address, - size_t address_length, G_GNUC_UNUSED int uid, void *ctx) -{ - struct httpd_output *httpd = ctx; - - /* the listener socket has become readable - a client has - connected */ - -#ifdef HAVE_LIBWRAP - if (address->sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(address, address_length, NULL); - const char *progname = g_get_prgname(); - - struct request_info req; - request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); - - fromhost(&req); - - if (!hosts_access(&req)) { - /* tcp wrappers says no */ - g_warning("libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - g_free(hostaddr); - close_socket(fd); - g_mutex_unlock(httpd->mutex); - return; - } - - g_free(hostaddr); - } -#else - (void)address; - (void)address_length; -#endif /* HAVE_WRAP */ - - g_mutex_lock(httpd->mutex); - - if (fd >= 0) { - /* can we allow additional client */ - if (httpd->open && - (httpd->clients_max == 0 || - httpd->clients_cnt < httpd->clients_max)) - httpd_client_add(httpd, fd); - else - close_socket(fd); - } else if (fd < 0 && errno != EINTR) { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - g_mutex_unlock(httpd->mutex); -} - -/** - * Reads data from the encoder (as much as available) and returns it - * as a new #page object. - */ -static struct page * -httpd_output_read_page(struct httpd_output *httpd) -{ - if (httpd->unflushed_input >= 65536) { - /* we have fed a lot of input into the encoder, but it - didn't give anything back yet - flush now to avoid - buffer underruns */ - encoder_flush(httpd->encoder, NULL); - httpd->unflushed_input = 0; - } - - size_t size = 0; - do { - size_t nbytes = encoder_read(httpd->encoder, - httpd->buffer + size, - sizeof(httpd->buffer) - size); - if (nbytes == 0) - break; - - httpd->unflushed_input = 0; - - size += nbytes; - } while (size < sizeof(httpd->buffer)); - - if (size == 0) - return NULL; - - return page_new_copy(httpd->buffer, size); -} - -static bool -httpd_output_encoder_open(struct httpd_output *httpd, - struct audio_format *audio_format, - GError **error) -{ - if (!encoder_open(httpd->encoder, audio_format, error)) - return false; - - /* we have to remember the encoder header, i.e. the first - bytes of encoder output after opening it, because it has to - be sent to every new client */ - httpd->header = httpd_output_read_page(httpd); - - httpd->unflushed_input = 0; - - return true; -} - -static bool -httpd_output_enable(struct audio_output *ao, GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - return httpd_output_bind(httpd, error_r); -} - -static void -httpd_output_disable(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - httpd_output_unbind(httpd); -} - -static bool -httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - /* open the encoder */ - - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); - return false; - } - - /* initialize other attributes */ - - httpd->clients = NULL; - httpd->clients_cnt = 0; - httpd->timer = timer_new(audio_format); - - httpd->open = true; - - g_mutex_unlock(httpd->mutex); - return true; -} - -static void -httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_free(client); -} - -static void -httpd_output_close(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - httpd->open = false; - - timer_free(httpd->timer); - - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); - - if (httpd->header != NULL) - page_unref(httpd->header); - - encoder_close(httpd->encoder); - - g_mutex_unlock(httpd->mutex); -} - -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client) -{ - assert(httpd != NULL); - assert(client != NULL); - - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; -} - -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) -{ - if (httpd->header != NULL) - httpd_client_send(client, httpd->header); -} - -static unsigned -httpd_output_delay(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { - /* if there's no client and this output is paused, - then httpd_output_pause() will not do anything, it - will not fill the buffer and it will not update the - timer; therefore, we reset the timer here */ - timer_reset(httpd->timer); - - /* some arbitrary delay that is long enough to avoid - consuming too much CPU, and short enough to notice - new clients quickly enough */ - return 1000; - } - - return httpd->timer->started - ? timer_delay(httpd->timer) - : 0; -} - -static void -httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - if (httpd_client_queue_size(client) > 256 * 1024) { - g_debug("client is too slow, flushing its queue"); - httpd_client_cancel(client); - } -} - -static void -httpd_client_send_page(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *page = user_data; - - httpd_client_send(client, page); -} - -/** - * Broadcasts a page struct to all clients. - */ -static void -httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page) -{ - assert(page != NULL); - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); -} - -/** - * Broadcasts data from the encoder to all clients. - */ -static void -httpd_output_encoder_to_clients(struct httpd_output *httpd) -{ - struct page *page; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_check_queue, NULL); - g_mutex_unlock(httpd->mutex); - - while ((page = httpd_output_read_page(httpd)) != NULL) { - httpd_output_broadcast_page(httpd, page); - page_unref(page); - } -} - -static bool -httpd_output_encode_and_play(struct httpd_output *httpd, - const void *chunk, size_t size, GError **error) -{ - if (!encoder_write(httpd->encoder, chunk, size, error)) - return false; - - httpd->unflushed_input += size; - - httpd_output_encoder_to_clients(httpd); - - return true; -} - -static size_t -httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) - return 0; - } - - if (!httpd->timer->started) - timer_start(httpd->timer); - timer_add(httpd->timer, size); - - return size; -} - -static bool -httpd_output_pause(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; - return httpd_output_play(ao, silence, sizeof(silence), - NULL) > 0; - } else { - return true; - } -} - -static void -httpd_send_metadata(gpointer data, gpointer user_data) -{ - struct httpd_client *client = data; - struct page *icy_metadata = user_data; - - httpd_client_send_metadata(client, icy_metadata); -} - -static void -httpd_output_tag(struct audio_output *ao, const struct tag *tag) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - assert(tag != NULL); - - if (httpd->encoder->plugin->tag != NULL) { - /* embed encoder tags */ - - /* flush the current stream, and end it */ - - encoder_pre_tag(httpd->encoder, NULL); - httpd_output_encoder_to_clients(httpd); - - /* send the tag to the encoder - which starts a new - stream now */ - - encoder_tag(httpd->encoder, tag, NULL); - - /* the first page generated by the encoder will now be - used as the new "header" page, which is sent to all - new clients */ - - struct page *page = httpd_output_read_page(httpd); - if (page != NULL) { - if (httpd->header != NULL) - page_unref(httpd->header); - httpd->header = page; - httpd_output_broadcast_page(httpd, page); - } - } else { - /* use Icy-Metadata */ - - if (httpd->metadata != NULL) - page_unref (httpd->metadata); - - httpd->metadata = - icy_server_metadata_page(tag, TAG_ALBUM, - TAG_ARTIST, TAG_TITLE, - TAG_NUM_OF_ITEM_TYPES); - if (httpd->metadata != NULL) { - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, - httpd_send_metadata, httpd->metadata); - g_mutex_unlock(httpd->mutex); - } - } -} - -static void -httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct httpd_client *client = data; - - httpd_client_cancel(client); -} - -static void -httpd_output_cancel(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); -} - -const struct audio_output_plugin httpd_output_plugin = { - .name = "httpd", - .init = httpd_output_init, - .finish = httpd_output_finish, - .enable = httpd_output_enable, - .disable = httpd_output_disable, - .open = httpd_output_open, - .close = httpd_output_close, - .delay = httpd_output_delay, - .send_tag = httpd_output_tag, - .play = httpd_output_play, - .pause = httpd_output_pause, - .cancel = httpd_output_cancel, -}; diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index e267427df..e82d3d3fc 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -20,8 +20,8 @@ #include "config.h" #include "pulse_output_plugin.h" #include "output_api.h" -#include "mixer_list.h" -#include "mixer/pulse_mixer_plugin.h" +#include "MixerList.hxx" +#include "mixer/PulseMixerPlugin.h" #include <glib.h> diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 02a51f27b..bcc8004a7 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -20,9 +20,9 @@ #ifndef MPD_PULSE_OUTPUT_PLUGIN_H #define MPD_PULSE_OUTPUT_PLUGIN_H -#include <stdbool.h> +#include "gerror.h" -#include <glib.h> +#include <stdbool.h> struct pulse_output; struct pulse_mixer; @@ -30,6 +30,10 @@ struct pa_cvolume; extern const struct audio_output_plugin pulse_output_plugin; +#ifdef __cplusplus +extern "C" { +#endif + void pulse_output_lock(struct pulse_output *po); @@ -46,4 +50,8 @@ bool pulse_output_set_volume(struct pulse_output *po, const struct pa_cvolume *volume, GError **error_r); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c index 56456a0ea..56d7a88b1 100644 --- a/src/output/shout_output_plugin.c +++ b/src/output/shout_output_plugin.c @@ -97,23 +97,22 @@ static void free_shout_data(struct shout_data *sd) g_free(sd); } -#define check_block_param(name) { \ - block_param = config_get_block_param(param, name); \ - if (!block_param) { \ - MPD_ERROR("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ - } \ - } +gcc_pure +static const char * +require_block_string(const struct config_param *param, const char *name) +{ + const char *value = config_get_block_string(param, name, NULL); + if (value == NULL) + MPD_ERROR("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ -static struct audio_output * -my_shout_init_driver(const struct config_param *param, - GError **error) + return value; +} + +static bool +my_shout_configure(struct shout_data *sd, const struct config_param *param, + GError **error) { - struct shout_data *sd = new_shout_data(); - if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { - free_shout_data(sd); - return NULL; - } const struct audio_format *audio_format = &sd->base.config_audio_format; @@ -125,30 +124,18 @@ my_shout_init_driver(const struct config_param *param, return NULL; } - if (shout_init_count == 0) - shout_init(); - - shout_init_count++; - - const struct block_param *block_param; - check_block_param("host"); - char *host = block_param->value; - - check_block_param("mount"); - char *mount = block_param->value; + const char *host = require_block_string(param, "host"); + const char *mount = require_block_string(param, "mount"); unsigned port = config_get_block_unsigned(param, "port", 0); if (port == 0) { g_set_error(error, shout_output_quark(), 0, "shout port must be configured"); - goto failure; + return false; } - check_block_param("password"); - const char *passwd = block_param->value; - - check_block_param("name"); - const char *name = block_param->value; + const char *passwd = require_block_string(param, "password"); + const char *name = require_block_string(param, "name"); bool public = config_get_block_bool(param, "public", false); @@ -164,21 +151,21 @@ my_shout_init_driver(const struct config_param *param, "shout quality \"%s\" is not a number in the " "range -1 to 10, line %i", value, param->line); - goto failure; + return false; } if (config_get_block_string(param, "bitrate", NULL) != NULL) { g_set_error(error, shout_output_quark(), 0, "quality and bitrate are " "both defined"); - goto failure; + return false; } } else { value = config_get_block_string(param, "bitrate", NULL); if (value == NULL) { g_set_error(error, shout_output_quark(), 0, "neither bitrate nor quality defined"); - goto failure; + return false; } char *test; @@ -187,7 +174,7 @@ my_shout_init_driver(const struct config_param *param, if (*test != '\0' || sd->bitrate <= 0) { g_set_error(error, shout_output_quark(), 0, "bitrate must be a positive integer"); - goto failure; + return false; } } @@ -199,12 +186,12 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "couldn't find shout encoder plugin \"%s\"", encoding); - goto failure; + return false; } sd->encoder = encoder_init(encoder_plugin, param, error); if (sd->encoder == NULL) - goto failure; + return false; unsigned shout_format; if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) @@ -220,7 +207,7 @@ my_shout_init_driver(const struct config_param *param, g_set_error(error, shout_output_quark(), 0, "you cannot stream \"%s\" to shoutcast, use mp3", encoding); - goto failure; + return false; } else if (0 == strcmp(value, "shoutcast")) protocol = SHOUT_PROTOCOL_ICY; else if (0 == strcmp(value, "icecast1")) @@ -232,7 +219,7 @@ my_shout_init_driver(const struct config_param *param, "shout protocol \"%s\" is not \"shoutcast\" or " "\"icecast1\"or \"icecast2\"", value); - goto failure; + return false; } } else { protocol = SHOUT_PROTOCOL_HTTP; @@ -251,7 +238,7 @@ my_shout_init_driver(const struct config_param *param, shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } /* optional paramters */ @@ -262,21 +249,21 @@ my_shout_init_driver(const struct config_param *param, if (value != NULL && shout_set_genre(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } value = config_get_block_string(param, "description", NULL); if (value != NULL && shout_set_description(sd->shout_conn, value)) { g_set_error(error, shout_output_quark(), 0, "%s", shout_get_error(sd->shout_conn)); - goto failure; + return false; } 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; + return false; } { @@ -301,12 +288,31 @@ my_shout_init_driver(const struct config_param *param, } } - return &sd->base; + return true; +} -failure: - ao_base_finish(&sd->base); - free_shout_data(sd); - return NULL; +static struct audio_output * +my_shout_init_driver(const struct config_param *param, + GError **error) +{ + struct shout_data *sd = new_shout_data(); + if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { + free_shout_data(sd); + return NULL; + } + + if (!my_shout_configure(sd, param, error)) { + ao_base_finish(&sd->base); + free_shout_data(sd); + return NULL; + } + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + return &sd->base; } static bool |