diff options
Diffstat (limited to 'src/output')
-rw-r--r-- | src/output/AlsaOutputPlugin.cxx (renamed from src/output/alsa_output_plugin.c) | 434 | ||||
-rw-r--r-- | src/output/AlsaOutputPlugin.hxx (renamed from src/output/alsa_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/AoOutputPlugin.cxx (renamed from src/output/ao_output_plugin.c) | 194 | ||||
-rw-r--r-- | src/output/AoOutputPlugin.hxx (renamed from src/output/ao_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/FifoOutputPlugin.cxx | 315 | ||||
-rw-r--r-- | src/output/FifoOutputPlugin.hxx (renamed from src/output/fifo_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/HttpdClient.cxx | 450 | ||||
-rw-r--r-- | src/output/HttpdClient.hxx | 186 | ||||
-rw-r--r-- | src/output/HttpdInternal.hxx (renamed from src/output/httpd_internal.h) | 144 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.cxx | 565 | ||||
-rw-r--r-- | src/output/HttpdOutputPlugin.hxx (renamed from src/output/httpd_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/JackOutputPlugin.cxx (renamed from src/output/jack_output_plugin.c) | 346 | ||||
-rw-r--r-- | src/output/JackOutputPlugin.hxx (renamed from src/output/jack_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/NullOutputPlugin.cxx | 143 | ||||
-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) | 226 | ||||
-rw-r--r-- | src/output/OSXOutputPlugin.hxx (renamed from src/output/osx_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/OpenALOutputPlugin.cxx (renamed from src/output/openal_output_plugin.c) | 148 | ||||
-rw-r--r-- | src/output/OpenALOutputPlugin.hxx (renamed from src/output/openal_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/OssOutputPlugin.cxx (renamed from src/output/oss_output_plugin.c) | 358 | ||||
-rw-r--r-- | src/output/OssOutputPlugin.hxx (renamed from src/output/oss_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/PipeOutputPlugin.cxx | 149 | ||||
-rw-r--r-- | src/output/PipeOutputPlugin.hxx (renamed from src/output/pipe_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/PulseOutputPlugin.cxx (renamed from src/output/pulse_output_plugin.c) | 508 | ||||
-rw-r--r-- | src/output/PulseOutputPlugin.hxx (renamed from src/output/pulse_output_plugin.h) | 27 | ||||
-rw-r--r-- | src/output/RecorderOutputPlugin.cxx | 262 | ||||
-rw-r--r-- | src/output/RecorderOutputPlugin.hxx (renamed from src/output/recorder_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/RoarOutputPlugin.cxx (renamed from src/output/roar_output_plugin.c) | 222 | ||||
-rw-r--r-- | src/output/RoarOutputPlugin.hxx (renamed from src/output/roar_output_plugin.h) | 10 | ||||
-rw-r--r-- | src/output/ShoutOutputPlugin.cxx | 544 | ||||
-rw-r--r-- | src/output/ShoutOutputPlugin.hxx (renamed from src/output/shout_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/SolarisOutputPlugin.cxx (renamed from src/output/solaris_output_plugin.c) | 110 | ||||
-rw-r--r-- | src/output/SolarisOutputPlugin.hxx (renamed from src/output/solaris_output_plugin.h) | 6 | ||||
-rw-r--r-- | src/output/WinmmOutputPlugin.cxx (renamed from src/output/winmm_output_plugin.c) | 190 | ||||
-rw-r--r-- | src/output/WinmmOutputPlugin.hxx (renamed from src/output/winmm_output_plugin.h) | 15 | ||||
-rw-r--r-- | src/output/ffado_output_plugin.c | 359 | ||||
-rw-r--r-- | src/output/ffado_output_plugin.h | 25 | ||||
-rw-r--r-- | src/output/fifo_output_plugin.c | 315 | ||||
-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/mvp_output_plugin.c | 344 | ||||
-rw-r--r-- | src/output/mvp_output_plugin.h | 25 | ||||
-rw-r--r-- | src/output/null_output_plugin.c | 129 | ||||
-rw-r--r-- | src/output/pipe_output_plugin.c | 121 | ||||
-rw-r--r-- | src/output/recorder_output_plugin.c | 251 | ||||
-rw-r--r-- | src/output/shout_output_plugin.c | 555 |
47 files changed, 4145 insertions, 5061 deletions
diff --git a/src/output/alsa_output_plugin.c b/src/output/AlsaOutputPlugin.cxx index d8b184273..79b81282b 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,39 +18,42 @@ */ #include "config.h" -#include "alsa_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "pcm_export.h" +#include "AlsaOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "pcm/PcmExport.hxx" +#include "util/Manual.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" #include <glib.h> #include <alsa/asoundlib.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "alsa" +#include <string> #define ALSA_PCM_NEW_HW_PARAMS_API #define ALSA_PCM_NEW_SW_PARAMS_API static const char default_device[] = "default"; -enum { - MPD_ALSA_BUFFER_TIME_US = 500000, -}; +static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000; #define MPD_ALSA_RETRY_NR 5 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; + Manual<PcmExport> 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,70 +104,71 @@ struct alsa_data { * The number of frames written in the current period. */ snd_pcm_uframes_t period_position; -}; -/** - * The quark used for GError.domain. - */ -static inline GQuark -alsa_output_quark(void) -{ - return g_quark_from_static_string("alsa_output"); -} + /** + * This buffer gets allocated after opening the ALSA device. + * It contains silence samples, enough to fill one period (see + * #period_frames). + */ + void *silence; -static const char * -alsa_device(const struct alsa_data *ad) -{ - return ad->device != NULL ? ad->device : default_device; -} + AlsaOutput():mode(0), writei(snd_pcm_writei) { + } -static struct alsa_data * -alsa_data_new(void) -{ - struct alsa_data *ret = g_new(struct alsa_data, 1); + bool Init(const config_param ¶m, Error &error) { + return ao_base_init(&base, &alsa_output_plugin, + param, error); + } - ret->mode = 0; - ret->writei = snd_pcm_writei; + void Deinit() { + ao_base_finish(&base); + } +}; + +static constexpr Domain alsa_output_domain("alsa_output"); - return ret; +static const char * +alsa_device(const AlsaOutput *ad) +{ + 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 config_param ¶m) { - ad->device = config_dup_block_string(param, "device", NULL); + ad->device = param.GetBlockValue("device", ""); - ad->use_mmap = config_get_block_bool(param, "use_mmap", false); + ad->use_mmap = param.GetBlockValue("use_mmap", false); - ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); + ad->dsd_usb = param.GetBlockValue("dsd_usb", false); - ad->buffer_time = config_get_block_unsigned(param, "buffer_time", - MPD_ALSA_BUFFER_TIME_US); - ad->period_time = config_get_block_unsigned(param, "period_time", 0); + ad->buffer_time = param.GetBlockValue("buffer_time", + MPD_ALSA_BUFFER_TIME_US); + ad->period_time = param.GetBlockValue("period_time", 0u); #ifdef SND_PCM_NO_AUTO_RESAMPLE - if (!config_get_block_bool(param, "auto_resample", true)) + if (!param.GetBlockValue("auto_resample", true)) ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; #endif #ifdef SND_PCM_NO_AUTO_CHANNELS - if (!config_get_block_bool(param, "auto_channels", true)) + if (!param.GetBlockValue("auto_channels", true)) ad->mode |= SND_PCM_NO_AUTO_CHANNELS; #endif #ifdef SND_PCM_NO_AUTO_FORMAT - if (!config_get_block_bool(param, "auto_format", true)) + if (!param.GetBlockValue("auto_format", true)) ad->mode |= SND_PCM_NO_AUTO_FORMAT; #endif } static struct audio_output * -alsa_init(const struct config_param *param, GError **error_r) +alsa_init(const config_param ¶m, Error &error) { - 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)) { + delete ad; return NULL; } @@ -176,32 +180,30 @@ 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; - - ao_base_finish(&ad->base); + AlsaOutput *ad = (AlsaOutput *)ao; - g_free(ad->device); - g_free(ad); + ad->Deinit(); + delete ad; /* free libasound's config cache */ snd_config_update_free_global(); } static bool -alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +alsa_output_enable(struct audio_output *ao, gcc_unused Error &error) { - struct alsa_data *ad = (struct alsa_data *)ao; + AlsaOutput *ad = (AlsaOutput *)ao; - pcm_export_init(&ad->export); + ad->pcm_export.Construct(); 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); + ad->pcm_export.Destruct(); } static bool @@ -212,8 +214,9 @@ alsa_test_default_device(void) int ret = snd_pcm_open(&handle, default_device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (ret) { - g_message("Error opening default ALSA device: %s\n", - snd_strerror(-ret)); + FormatError(alsa_output_domain, + "Error opening default ALSA device: %s", + snd_strerror(-ret)); return false; } else snd_pcm_close(handle); @@ -222,31 +225,31 @@ alsa_test_default_device(void) } static snd_pcm_format_t -get_bitformat(enum sample_format sample_format) +get_bitformat(SampleFormat sample_format) { switch (sample_format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_DSD: + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: return SND_PCM_FORMAT_UNKNOWN; - case SAMPLE_FORMAT_S8: + case SampleFormat::S8: return SND_PCM_FORMAT_S8; - case SAMPLE_FORMAT_S16: + case SampleFormat::S16: return SND_PCM_FORMAT_S16; - case SAMPLE_FORMAT_S24_P32: + case SampleFormat::S24_P32: return SND_PCM_FORMAT_S24; - case SAMPLE_FORMAT_S32: + case SampleFormat::S32: return SND_PCM_FORMAT_S32; - case SAMPLE_FORMAT_FLOAT: + case SampleFormat::FLOAT: return SND_PCM_FORMAT_FLOAT; } assert(false); - return SND_PCM_FORMAT_UNKNOWN; + gcc_unreachable(); } static snd_pcm_format_t @@ -313,7 +316,7 @@ alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, */ static int alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - enum sample_format sample_format, + SampleFormat sample_format, bool *packed_r, bool *reverse_endian_r) { snd_pcm_format_t alsa_format = get_bitformat(sample_format); @@ -344,35 +347,36 @@ alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, */ static int alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, + AudioFormat &audio_format, bool *packed_r, bool *reverse_endian_r) { /* try the input format first */ - int err = alsa_output_try_format(pcm, hwparams, audio_format->format, + int err = alsa_output_try_format(pcm, hwparams, + audio_format.format, packed_r, reverse_endian_r); /* if unsupported by the hardware, try other formats */ - static const enum sample_format probe_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED, + static const SampleFormat probe_formats[] = { + SampleFormat::S24_P32, + SampleFormat::S32, + SampleFormat::S16, + SampleFormat::S8, + SampleFormat::UNDEFINED, }; for (unsigned i = 0; - err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; + err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED; ++i) { - const enum sample_format mpd_format = probe_formats[i]; - if (mpd_format == audio_format->format) + const SampleFormat mpd_format = probe_formats[i]; + if (mpd_format == audio_format.format) continue; err = alsa_output_try_format(pcm, hwparams, mpd_format, packed_r, reverse_endian_r); if (err == 0) - audio_format->format = mpd_format; + audio_format.format = mpd_format; } return err; @@ -383,15 +387,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, - bool *packed_r, bool *reverse_endian_r, GError **error) +alsa_setup(AlsaOutput *ad, AudioFormat &audio_format, + bool *packed_r, bool *reverse_endian_r, Error &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; + unsigned int sample_rate = audio_format.sample_rate; + unsigned int channels = audio_format.channels; int err; const char *cmd = NULL; int retry = MPD_ALSA_RETRY_NR; @@ -401,6 +401,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); @@ -411,9 +412,11 @@ configure_hw: err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED); if (err < 0) { - g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n", - alsa_device(ad), snd_strerror(-err)); - g_warning("Falling back to direct write mode\n"); + FormatWarning(alsa_output_domain, + "Cannot set mmap'ed mode on ALSA device \"%s\": %s", + alsa_device(ad), snd_strerror(-err)); + LogWarning(alsa_output_domain, + "Falling back to direct write mode"); ad->use_mmap = false; } else ad->writei = snd_pcm_mmap_writei; @@ -431,39 +434,40 @@ configure_hw: err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, packed_r, reverse_endian_r); if (err < 0) { - 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), - snd_strerror(-err)); + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(audio_format.format), + snd_strerror(-err)); return false; } snd_pcm_format_t format; if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) - g_debug("format=%s (%s)", snd_pcm_format_name(format), - snd_pcm_format_description(format)); + FormatDebug(alsa_output_domain, + "format=%s (%s)", snd_pcm_format_name(format), + snd_pcm_format_description(format)); err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, &channels); if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %i channels: %s", - alsa_device(ad), (int)audio_format->channels, - snd_strerror(-err)); + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support %i channels: %s", + alsa_device(ad), (int)audio_format.channels, + snd_strerror(-err)); return false; } - audio_format->channels = (int8_t)channels; + audio_format.channels = (int8_t)channels; err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, &sample_rate, NULL); if (err < 0 || sample_rate == 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %u Hz audio", - alsa_device(ad), audio_format->sample_rate); + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support %u Hz audio", + alsa_device(ad), audio_format.sample_rate); return false; } - audio_format->sample_rate = sample_rate; + audio_format.sample_rate = sample_rate; snd_pcm_uframes_t buffer_size_min, buffer_size_max; snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); @@ -471,9 +475,9 @@ configure_hw: unsigned buffer_time_min, buffer_time_max; snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); - g_debug("buffer: size=%u..%u time=%u..%u", - (unsigned)buffer_size_min, (unsigned)buffer_size_max, - buffer_time_min, buffer_time_max); + FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u", + (unsigned)buffer_size_min, (unsigned)buffer_size_max, + buffer_time_min, buffer_time_max); snd_pcm_uframes_t period_size_min, period_size_max; snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); @@ -481,9 +485,9 @@ configure_hw: unsigned period_time_min, period_time_max; snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); - g_debug("period: size=%u..%u time=%u..%u", - (unsigned)period_size_min, (unsigned)period_size_max, - period_time_min, period_time_max); + FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u", + (unsigned)period_size_min, (unsigned)period_size_max, + period_time_min, period_time_max); if (ad->buffer_time > 0) { buffer_time = ad->buffer_time; @@ -502,8 +506,9 @@ configure_hw: if (period_time_ro == 0 && buffer_time >= 10000) { period_time_ro = period_time = buffer_time / 4; - g_debug("default period_time = buffer_time/4 = %u/4 = %u", - buffer_time, period_time); + FormatDebug(alsa_output_domain, + "default period_time = buffer_time/4 = %u/4 = %u", + buffer_time, period_time); } if (period_time_ro > 0) { @@ -523,13 +528,16 @@ configure_hw: } else if (err < 0) goto error; if (retry != MPD_ALSA_RETRY_NR) - g_debug("ALSA period_time set to %d\n", period_time); + FormatDebug(alsa_output_domain, + "ALSA period_time set to %d", 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 +545,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"; @@ -562,8 +571,8 @@ configure_hw: if (err < 0) goto error; - g_debug("buffer_size=%u period_size=%u", - (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u", + (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); if (alsa_period_size == 0) /* this works around a SIGFPE bug that occurred when @@ -576,32 +585,37 @@ 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: - g_set_error(error, alsa_output_quark(), err, - "Error opening ALSA device \"%s\" (%s): %s", - alsa_device(ad), cmd, snd_strerror(-err)); + error.Format(alsa_output_domain, err, + "Error opening ALSA device \"%s\" (%s): %s", + alsa_device(ad), cmd, snd_strerror(-err)); return false; } static bool -alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, +alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format, bool *shift8_r, bool *packed_r, bool *reverse_endian_r, - GError **error_r) + Error &error) { assert(ad->dsd_usb); - assert(audio_format->format == SAMPLE_FORMAT_DSD); + assert(audio_format.format == SampleFormat::DSD); /* pass 24 bit to alsa_setup() */ - struct audio_format usb_format = *audio_format; - usb_format.format = SAMPLE_FORMAT_S24_P32; + AudioFormat usb_format = audio_format; + usb_format.format = SampleFormat::S24_P32; usb_format.sample_rate /= 2; - const struct audio_format check = usb_format; + const AudioFormat check = usb_format; - if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) + if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error)) return false; /* if the device allows only 32 bit, shift all DSD-over-USB @@ -609,16 +623,17 @@ alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, the DSD-over-USB documentation does not specify whether this is legal, but there is anecdotical evidence that this is possible (and the only option for some devices) */ - *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; - if (usb_format.format == SAMPLE_FORMAT_S32) - usb_format.format = SAMPLE_FORMAT_S24_P32; + *shift8_r = usb_format.format == SampleFormat::S32; + if (usb_format.format == SampleFormat::S32) + usb_format.format = SampleFormat::S24_P32; - if (!audio_format_equals(&usb_format, &check)) { + if (usb_format != check) { /* no bit-perfect playback, which is required for DSD over USB */ - g_set_error(error_r, alsa_output_quark(), 0, - "Failed to configure DSD-over-USB on ALSA device \"%s\"", - alsa_device(ad)); + error.Format(alsa_output_domain, + "Failed to configure DSD-over-USB on ALSA device \"%s\"", + alsa_device(ad)); + g_free(ad->silence); return false; } @@ -626,66 +641,76 @@ 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, - GError **error_r) +alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format, + Error &error) { bool shift8 = false, packed, reverse_endian; const bool dsd_usb = ad->dsd_usb && - audio_format->format == SAMPLE_FORMAT_DSD; + audio_format.format == SampleFormat::DSD; const bool success = dsd_usb ? alsa_setup_dsd(ad, audio_format, &shift8, &packed, &reverse_endian, - error_r) + error) : alsa_setup(ad, audio_format, &packed, &reverse_endian, - error_r); + error); if (!success) return false; - pcm_export_open(&ad->export, - audio_format->format, audio_format->channels, - dsd_usb, shift8, packed, reverse_endian); + ad->pcm_export->Open(audio_format.format, + audio_format.channels, + dsd_usb, shift8, packed, reverse_endian); return true; } static bool -alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &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, + error.Format(alsa_output_domain, err, "Failed to open ALSA device \"%s\": %s", alsa_device(ad), snd_strerror(err)); return false; } - g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), - snd_pcm_type_name(snd_pcm_type(ad->pcm))); + FormatDebug(alsa_output_domain, "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->in_frame_size = audio_format.GetFrameSize(); + ad->out_frame_size = ad->pcm_export->GetFrameSize(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)); + FormatDebug(alsa_output_domain, + "Underrun on ALSA device \"%s\"", alsa_device(ad)); } else if (err == -ESTRPIPE) { - g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad)); + FormatDebug(alsa_output_domain, + "ALSA device \"%s\" was suspended", + alsa_device(ad)); } switch (snd_pcm_state(ad->pcm)) { @@ -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) + Error &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 = ad->pcm_export->Export(chunk, size, size); assert(size % ad->out_frame_size == 0); @@ -789,31 +822,32 @@ 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, - bytes_written); + return ad->pcm_export->CalcSourceSize(bytes_written); } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && alsa_recover(ad, ret) < 0) { - g_set_error(error, alsa_output_quark(), errno, - "%s", snd_strerror(-errno)); + error.Set(alsa_output_domain, ret, snd_strerror(-ret)); return 0; } } } 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/ao_output_plugin.c b/src/output/AoOutputPlugin.cxx index d7e577fa4..e66969e20 100644 --- a/src/output/ao_output_plugin.c +++ b/src/output/AoOutputPlugin.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,37 +18,46 @@ */ #include "config.h" -#include "ao_output_plugin.h" -#include "output_api.h" +#include "AoOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" #include <ao/ao.h> #include <glib.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ao" +#include <string.h> /* An ao_sample_format, with all fields set to zero: */ -static const ao_sample_format OUR_AO_FORMAT_INITIALIZER; +static ao_sample_format OUR_AO_FORMAT_INITIALIZER; static unsigned ao_output_ref; -struct ao_data { +struct AoOutput { struct audio_output base; size_t write_size; int driver; ao_option *options; ao_device *device; -} AoData; -static inline GQuark -ao_output_quark(void) -{ - return g_quark_from_static_string("ao_output"); -} + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &ao_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain ao_output_domain("ao_output"); static void -ao_output_error(GError **error_r) +ao_output_error(Error &error_r) { const char *error; @@ -74,85 +83,89 @@ ao_output_error(GError **error_r) break; default: - error = g_strerror(errno); + error_r.SetErrno(); + return; } - g_set_error(error_r, ao_output_quark(), errno, - "%s", error); + error_r.Set(ao_output_domain, errno, error); } -static struct audio_output * -ao_output_init(const struct config_param *param, - GError **error) +inline bool +AoOutput::Configure(const config_param ¶m, Error &error) { - struct ao_data *ad = g_new(struct ao_data, 1); - - if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) { - g_free(ad); - return NULL; - } - - ao_info *ai; const char *value; - ad->options = NULL; + options = nullptr; - ad->write_size = config_get_block_unsigned(param, "write_size", 1024); + write_size = param.GetBlockValue("write_size", 1024u); if (ao_output_ref == 0) { ao_initialize(); } ao_output_ref++; - value = config_get_block_string(param, "driver", "default"); + value = param.GetBlockValue("driver", "default"); if (0 == strcmp(value, "default")) - ad->driver = ao_default_driver_id(); + driver = ao_default_driver_id(); else - ad->driver = ao_driver_id(value); - - if (ad->driver < 0) { - g_set_error(error, ao_output_quark(), 0, - "\"%s\" is not a valid ao driver", - value); - ao_base_finish(&ad->base); - g_free(ad); - return NULL; + driver = ao_driver_id(value); + + if (driver < 0) { + error.Format(ao_output_domain, + "\"%s\" is not a valid ao driver", + value); + return false; } - if ((ai = ao_driver_info(ad->driver)) == NULL) { - g_set_error(error, ao_output_quark(), 0, - "problems getting driver info"); - ao_base_finish(&ad->base); - g_free(ad); - return NULL; + ao_info *ai = ao_driver_info(driver); + if (ai == nullptr) { + error.Set(ao_output_domain, "problems getting driver info"); + return false; } - g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name, - config_get_block_string(param, "name", NULL)); + FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n", + ai->short_name, param.GetBlockValue("name", nullptr)); - value = config_get_block_string(param, "options", NULL); - if (value != NULL) { - gchar **options = g_strsplit(value, ";", 0); + value = param.GetBlockValue("options", nullptr); + if (value != nullptr) { + gchar **_options = g_strsplit(value, ";", 0); - for (unsigned i = 0; options[i] != NULL; ++i) { - gchar **key_value = g_strsplit(options[i], "=", 2); + for (unsigned i = 0; _options[i] != nullptr; ++i) { + gchar **key_value = g_strsplit(_options[i], "=", 2); - if (key_value[0] == NULL || key_value[1] == NULL) { - g_set_error(error, ao_output_quark(), 0, - "problems parsing options \"%s\"", - options[i]); - ao_base_finish(&ad->base); - g_free(ad); - return NULL; + if (key_value[0] == nullptr || key_value[1] == nullptr) { + error.Format(ao_output_domain, + "problems parsing options \"%s\"", + _options[i]); + return false; } - ao_append_option(&ad->options, key_value[0], + ao_append_option(&options, key_value[0], key_value[1]); g_strfreev(key_value); } - g_strfreev(options); + g_strfreev(_options); + } + + return true; +} + +static struct audio_output * +ao_output_init(const config_param ¶m, Error &error) +{ + AoOutput *ad = new AoOutput(); + + if (!ad->Initialize(param, error)) { + delete ad; + return nullptr; + } + + if (!ad->Configure(param, error)) { + ad->Deinitialize(); + delete ad; + return nullptr; } return &ad->base; @@ -161,11 +174,11 @@ ao_output_init(const struct config_param *param, static void ao_output_finish(struct audio_output *ao) { - struct ao_data *ad = (struct ao_data *)ao; + AoOutput *ad = (AoOutput *)ao; ao_free_options(ad->options); - ao_base_finish(&ad->base); - g_free(ad); + ad->Deinitialize(); + delete ad; ao_output_ref--; @@ -176,24 +189,24 @@ ao_output_finish(struct audio_output *ao) static void ao_output_close(struct audio_output *ao) { - struct ao_data *ad = (struct ao_data *)ao; + AoOutput *ad = (AoOutput *)ao; ao_close(ad->device); } static bool -ao_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) +ao_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; - struct ao_data *ad = (struct ao_data *)ao; + AoOutput *ad = (AoOutput *)ao; - switch (audio_format->format) { - case SAMPLE_FORMAT_S8: + switch (audio_format.format) { + case SampleFormat::S8: format.bits = 8; break; - case SAMPLE_FORMAT_S16: + case SampleFormat::S16: format.bits = 16; break; @@ -201,18 +214,18 @@ ao_output_open(struct audio_output *ao, struct audio_format *audio_format, /* support for 24 bit samples in libao is currently dubious, and until we have sorted that out, convert everything to 16 bit */ - audio_format->format = SAMPLE_FORMAT_S16; + audio_format.format = SampleFormat::S16; format.bits = 16; break; } - format.rate = audio_format->sample_rate; + format.rate = audio_format.sample_rate; format.byte_format = AO_FMT_NATIVE; - format.channels = audio_format->channels; + format.channels = audio_format.channels; ad->device = ao_open_live(ad->driver, &format, ad->options); - if (ad->device == NULL) { + if (ad->device == nullptr) { ao_output_error(error); return false; } @@ -230,7 +243,7 @@ static int ao_play_deconst(ao_device *device, const void *output_samples, { union { const void *in; - void *out; + char *out; } u; u.in = output_samples; @@ -239,9 +252,9 @@ static int ao_play_deconst(ao_device *device, const void *output_samples, static size_t ao_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) + Error &error) { - struct ao_data *ad = (struct ao_data *)ao; + AoOutput *ad = (AoOutput *)ao; if (size > ad->write_size) size = ad->write_size; @@ -255,10 +268,19 @@ ao_output_play(struct audio_output *ao, const void *chunk, size_t size, } const struct audio_output_plugin ao_output_plugin = { - .name = "ao", - .init = ao_output_init, - .finish = ao_output_finish, - .open = ao_output_open, - .close = ao_output_close, - .play = ao_output_play, + "ao", + nullptr, + ao_output_init, + ao_output_finish, + nullptr, + nullptr, + ao_output_open, + ao_output_close, + nullptr, + nullptr, + ao_output_play, + nullptr, + nullptr, + nullptr, + nullptr, }; diff --git a/src/output/ao_output_plugin.h b/src/output/AoOutputPlugin.hxx index 9a3a47c05..a44885e56 100644 --- a/src/output/ao_output_plugin.h +++ b/src/output/AoOutputPlugin.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_AO_OUTPUT_PLUGIN_H -#define MPD_AO_OUTPUT_PLUGIN_H +#ifndef MPD_AO_OUTPUT_PLUGIN_HXX +#define MPD_AO_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin ao_output_plugin; diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx new file mode 100644 index 000000000..babda5f9e --- /dev/null +++ b/src/output/FifoOutputPlugin.cxx @@ -0,0 +1,315 @@ +/* + * 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 "FifoOutputPlugin.hxx" +#include "ConfigError.hxx" +#include "OutputAPI.hxx" +#include "Timer.hxx" +#include "system/fd_util.h" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "open.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ + +struct FifoOutput { + struct audio_output base; + + Path path; + std::string path_utf8; + + int input; + int output; + bool created; + Timer *timer; + + FifoOutput() + :path(Path::Null()), input(-1), output(-1), created(false) {} + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &fifo_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Create(Error &error); + bool Check(Error &error); + void Delete(); + + bool Open(Error &error); + void Close(); +}; + +static constexpr Domain fifo_output_domain("fifo_output"); + +inline void +FifoOutput::Delete() +{ + FormatDebug(fifo_output_domain, + "Removing FIFO \"%s\"", path_utf8.c_str()); + + if (!RemoveFile(path)) { + FormatErrno(fifo_output_domain, + "Could not remove FIFO \"%s\"", + path_utf8.c_str()); + return; + } + + created = false; +} + +void +FifoOutput::Close() +{ + if (input >= 0) { + close(input); + input = -1; + } + + if (output >= 0) { + close(output); + output = -1; + } + + struct stat st; + if (created && StatFile(path, st)) + Delete(); +} + +inline bool +FifoOutput::Create(Error &error) +{ + if (!MakeFifo(path, 0666)) { + error.FormatErrno("Couldn't create FIFO \"%s\"", + path_utf8.c_str()); + return false; + } + + created = true; + return true; +} + +inline bool +FifoOutput::Check(Error &error) +{ + struct stat st; + if (!StatFile(path, st)) { + if (errno == ENOENT) { + /* Path doesn't exist */ + return Create(error); + } + + error.FormatErrno("Failed to stat FIFO \"%s\"", + path_utf8.c_str()); + return false; + } + + if (!S_ISFIFO(st.st_mode)) { + error.Format(fifo_output_domain, + "\"%s\" already exists, but is not a FIFO", + path_utf8.c_str()); + return false; + } + + return true; +} + +inline bool +FifoOutput::Open(Error &error) +{ + if (!Check(error)) + return false; + + input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0); + if (input < 0) { + error.FormatErrno("Could not open FIFO \"%s\" for reading", + path_utf8.c_str()); + Close(); + return false; + } + + output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0); + if (output < 0) { + error.FormatErrno("Could not open FIFO \"%s\" for writing", + path_utf8.c_str()); + Close(); + return false; + } + + return true; +} + +static bool +fifo_open(FifoOutput *fd, Error &error) +{ + return fd->Open(error); +} + +static struct audio_output * +fifo_output_init(const config_param ¶m, Error &error) +{ + FifoOutput *fd = new FifoOutput(); + + fd->path = param.GetBlockPath("path", error); + if (fd->path.IsNull()) { + delete fd; + + if (!error.IsDefined()) + error.Set(config_domain, + "No \"path\" parameter specified"); + return nullptr; + } + + fd->path_utf8 = fd->path.ToUTF8(); + + if (!fd->Initialize(param, error)) { + delete fd; + return nullptr; + } + + if (!fifo_open(fd, error)) { + fd->Deinitialize(); + delete fd; + return nullptr; + } + + return &fd->base; +} + +static void +fifo_output_finish(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + fd->Close(); + fd->Deinitialize(); + delete fd; +} + +static bool +fifo_output_open(struct audio_output *ao, AudioFormat &audio_format, + gcc_unused Error &error) +{ + FifoOutput *fd = (FifoOutput *)ao; + + fd->timer = new Timer(audio_format); + + return true; +} + +static void +fifo_output_close(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + delete fd->timer; +} + +static void +fifo_output_cancel(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + char buf[FIFO_BUFFER_SIZE]; + int bytes = 1; + + fd->timer->Reset(); + + while (bytes > 0 && errno != EINTR) + bytes = read(fd->input, buf, FIFO_BUFFER_SIZE); + + if (bytes < 0 && errno != EAGAIN) { + FormatErrno(fifo_output_domain, + "Flush of FIFO \"%s\" failed", + fd->path_utf8.c_str()); + } +} + +static unsigned +fifo_output_delay(struct audio_output *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + return fd->timer->IsStarted() + ? fd->timer->GetDelay() + : 0; +} + +static size_t +fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + FifoOutput *fd = (FifoOutput *)ao; + ssize_t bytes; + + if (!fd->timer->IsStarted()) + fd->timer->Start(); + fd->timer->Add(size); + + while (true) { + bytes = write(fd->output, chunk, size); + if (bytes > 0) + return (size_t)bytes; + + if (bytes < 0) { + switch (errno) { + case EAGAIN: + /* The pipe is full, so empty it */ + fifo_output_cancel(&fd->base); + continue; + case EINTR: + continue; + } + + error.FormatErrno("Failed to write to FIFO %s", + fd->path_utf8.c_str()); + return 0; + } + } +} + +const struct audio_output_plugin fifo_output_plugin = { + "fifo", + nullptr, + fifo_output_init, + fifo_output_finish, + nullptr, + nullptr, + fifo_output_open, + fifo_output_close, + fifo_output_delay, + nullptr, + fifo_output_play, + nullptr, + fifo_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/fifo_output_plugin.h b/src/output/FifoOutputPlugin.hxx index 85f7985e1..dca2886d8 100644 --- a/src/output/fifo_output_plugin.h +++ b/src/output/FifoOutputPlugin.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_FIFO_OUTPUT_PLUGIN_H -#define MPD_FIFO_OUTPUT_PLUGIN_H +#ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX +#define MPD_FIFO_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin fifo_output_plugin; diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx new file mode 100644 index 000000000..f7ae8d10c --- /dev/null +++ b/src/output/HttpdClient.cxx @@ -0,0 +1,450 @@ +/* + * 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 "system/SocketError.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +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 */ + LogWarning(httpd_output_domain, + "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) { + 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 */ + 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; + FormatWarning(httpd_output_domain, + "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); + FormatWarning(httpd_output_domain, + "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); + FormatWarning(httpd_output_domain, + "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); + FormatWarning(httpd_output_domain, + "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) { + LogWarning(httpd_output_domain, + "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(Error &&error) +{ + LogError(error); +} + +void +HttpdClient::OnSocketClosed() +{ + LockClose(); +} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx new file mode 100644 index 000000000..a596814ee --- /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 + */ + unsigned 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. + */ + unsigned 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(Error &&error) override; + virtual void OnSocketClosed() override; +}; + +#endif diff --git a/src/output/httpd_internal.h b/src/output/HttpdInternal.hxx index 5dcb8ab9b..445e2ff1a 100644 --- a/src/output/httpd_internal.h +++ b/src/output/HttpdInternal.hxx @@ -25,16 +25,23 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H -#include "output_internal.h" -#include "timer.h" - -#include <glib.h> - -#include <stdbool.h> - -struct httpd_client; - -struct httpd_output { +#include "OutputInternal.hxx" +#include "Timer.hxx" +#include "thread/Mutex.hxx" +#include "event/ServerSocket.hxx" + +#include <forward_list> + +struct config_param; +class Error; +class EventLoop; +class ServerSocket; +class HttpdClient; +class Page; +struct Encoder; +struct Tag; + +struct HttpdOutput final : private ServerSocket { struct audio_output base; /** @@ -46,7 +53,7 @@ struct httpd_output { /** * The configured encoder plugin. */ - struct encoder *encoder; + Encoder *encoder; /** * Number of bytes which were fed into the encoder, without @@ -65,28 +72,23 @@ 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 + * A #Timer object to synchronize this output with the * wallclock. */ - struct timer *timer; - - /** - * The listener socket. - */ - struct server_socket *server_socket; + Timer *timer; /** * 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 +107,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() @@ -117,22 +119,90 @@ struct httpd_output { * The maximum and current number of clients connected * at the same time. */ - guint clients_max, clients_cnt; -}; + unsigned 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 ¶m, Error &error); + + bool Bind(Error &error); + void Unbind(); + + /** + * Caller must lock the mutex. + */ + bool OpenEncoder(AudioFormat &audio_format, Error &error); + + /** + * Caller must lock the mutex. + */ + bool Open(AudioFormat &audio_format, Error &error); + + /** + * 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, Error &error); + + void SendTag(const Tag *tag); + +private: + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) override; +}; + +extern const class Domain httpd_output_domain; #endif diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx new file mode 100644 index 000000000..09f0f5e6b --- /dev/null +++ b/src/output/HttpdOutputPlugin.cxx @@ -0,0 +1,565 @@ +/* + * 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 "OutputAPI.hxx" +#include "EncoderPlugin.hxx" +#include "EncoderList.hxx" +#include "system/Resolver.hxx" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "system/fd_util.h" +#include "Main.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <assert.h> + +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#ifdef HAVE_LIBWRAP +#include <sys/socket.h> /* needed for AF_UNIX */ +#include <tcpd.h> +#endif + +const Domain httpd_output_domain("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(Error &error) +{ + open = false; + + const ScopeLock protect(mutex); + return ServerSocket::Open(error); +} + +inline void +HttpdOutput::Unbind() +{ + assert(!open); + + const ScopeLock protect(mutex); + ServerSocket::Close(); +} + +inline bool +HttpdOutput::Configure(const config_param ¶m, Error &error) +{ + /* read configuration */ + name = param.GetBlockValue("name", "Set name in config"); + genre = param.GetBlockValue("genre", "Set genre in config"); + website = param.GetBlockValue("website", "Set website in config"); + + unsigned port = param.GetBlockValue("port", 8000u); + + const char *encoder_name = + param.GetBlockValue("encoder", "vorbis"); + const auto encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + error.Format(httpd_output_domain, + "No such encoder: %s", encoder_name); + return false; + } + + clients_max = param.GetBlockValue("max_clients", 0u); + + /* set up bind_to_address */ + + const char *bind_to_address = param.GetBlockValue("bind_to_address"); + bool success = bind_to_address != NULL && + strcmp(bind_to_address, "any") != 0 + ? AddHost(bind_to_address, port, error) + : AddPort(port, error); + if (!success) + return false; + + /* initialize encoder */ + + encoder = encoder_init(*encoder_plugin, param, error); + 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 config_param ¶m, Error &error) +{ + HttpdOutput *httpd = new HttpdOutput(*main_loop); + + if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, + error)) { + delete httpd; + return nullptr; + } + + if (!httpd->Configure(param, error)) { + 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 == nullptr); + ++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, + IgnoreError()); + 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 */ + FormatWarning(httpd_output_domain, + "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) { + LogErrno(httpd_output_domain, "accept() failed"); + } +} + +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, IgnoreError()); + 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, Error &error) +{ + HttpdOutput *httpd = Cast(ao); + + return httpd->Bind(error); +} + +static void +httpd_output_disable(struct audio_output *ao) +{ + HttpdOutput *httpd = Cast(ao); + + httpd->Unbind(); +} + +inline bool +HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &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(AudioFormat &audio_format, Error &error) +{ + assert(!open); + assert(clients.empty()); + + /* open the encoder */ + + if (!OpenEncoder(audio_format, error)) + return false; + + /* initialize other attributes */ + + clients_cnt = 0; + timer = new Timer(audio_format); + + open = true; + + return true; +} + +static bool +httpd_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &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; + + delete 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 */ + httpd->timer->Reset(); + + /* 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->IsStarted() + ? httpd->timer->GetDelay() + : 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) { + FormatDebug(httpd_output_domain, + "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, Error &error) +{ + if (!encoder_write(encoder, chunk, size, error)) + return false; + + unflushed_input += size; + + BroadcastFromEncoder(); + return true; +} + +static size_t +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + HttpdOutput *httpd = Cast(ao); + + if (httpd->LockHasClients()) { + if (!httpd->EncodeAndPlay(chunk, size, error)) + return 0; + } + + if (!httpd->timer->IsStarted()) + httpd->timer->Start(); + httpd->timer->Add(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), + IgnoreError()) > 0; + } else { + return true; + } +} + +inline void +HttpdOutput::SendTag(const Tag *tag) +{ + assert(tag != NULL); + + if (encoder->plugin.tag != nullptr) { + /* embed encoder tags */ + + /* flush the current stream, and end it */ + + encoder_pre_tag(encoder, IgnoreError()); + BroadcastFromEncoder(); + + /* send the tag to the encoder - which starts a new + stream now */ + + encoder_tag(encoder, tag, IgnoreError()); + + /* 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 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/jack_output_plugin.c b/src/output/JackOutputPlugin.cxx index d5c8ca412..75c7daf81 100644 --- a/src/output/jack_output_plugin.c +++ b/src/output/JackOutputPlugin.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,8 +18,12 @@ */ #include "config.h" -#include "jack_output_plugin.h" -#include "output_api.h" +#include "JackOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" #include <assert.h> @@ -29,21 +33,19 @@ #include <jack/ringbuffer.h> #include <stdlib.h> +#include <string.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <errno.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "jack" - enum { MAX_PORTS = 16, }; static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); -struct jack_data { +struct JackOutput { struct audio_output base; /** @@ -66,7 +68,7 @@ struct jack_data { size_t ringbuffer_size; /* the current audio format */ - struct audio_format audio_format; + AudioFormat audio_format; /* jack library stuff */ jack_port_t *ports[MAX_PORTS]; @@ -80,23 +82,25 @@ struct jack_data { * silence. */ bool pause; + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &jack_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } }; -/** - * The quark used for GError.domain. - */ -static inline GQuark -jack_output_quark(void) -{ - return g_quark_from_static_string("jack_output"); -} +static constexpr Domain jack_output_domain("jack_output"); /** * Determine the number of frames guaranteed to be available on all * channels. */ static jack_nframes_t -mpd_jack_available(const struct jack_data *jd) +mpd_jack_available(const JackOutput *jd) { size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); @@ -114,8 +118,7 @@ mpd_jack_available(const struct jack_data *jd) static int mpd_jack_process(jack_nframes_t nframes, void *arg) { - struct jack_data *jd = (struct jack_data *) arg; - jack_default_audio_sample_t *out; + JackOutput *jd = (JackOutput *) arg; if (nframes <= 0) return 0; @@ -131,7 +134,9 @@ mpd_jack_process(jack_nframes_t nframes, void *arg) /* generate silence while MPD is paused */ for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - out = jack_port_get_buffer(jd->ports[i], nframes); + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); for (jack_nframes_t f = 0; f < nframes; ++f) out[f] = 0.0; @@ -145,8 +150,10 @@ mpd_jack_process(jack_nframes_t nframes, void *arg) available = nframes; for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - out = jack_port_get_buffer(jd->ports[i], nframes); - if (out == NULL) + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) /* workaround for libjack1 bug: if the server connection fails, the process callback is invoked anyway, but unable to get a @@ -165,8 +172,10 @@ mpd_jack_process(jack_nframes_t nframes, void *arg) for (unsigned i = jd->audio_format.channels; i < jd->num_source_ports; ++i) { - out = jack_port_get_buffer(jd->ports[i], nframes); - if (out == NULL) + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) /* workaround for libjack1 bug: if the server connection fails, the process callback is invoked anyway, but unable to get a @@ -183,36 +192,36 @@ mpd_jack_process(jack_nframes_t nframes, void *arg) static void mpd_jack_shutdown(void *arg) { - struct jack_data *jd = (struct jack_data *) arg; + JackOutput *jd = (JackOutput *) arg; jd->shutdown = true; } static void -set_audioformat(struct jack_data *jd, struct audio_format *audio_format) +set_audioformat(JackOutput *jd, AudioFormat &audio_format) { - audio_format->sample_rate = jack_get_sample_rate(jd->client); + audio_format.sample_rate = jack_get_sample_rate(jd->client); if (jd->num_source_ports == 1) - audio_format->channels = 1; - else if (audio_format->channels > jd->num_source_ports) - audio_format->channels = 2; + audio_format.channels = 1; + else if (audio_format.channels > jd->num_source_ports) + audio_format.channels = 2; - if (audio_format->format != SAMPLE_FORMAT_S16 && - audio_format->format != SAMPLE_FORMAT_S24_P32) - audio_format->format = SAMPLE_FORMAT_S24_P32; + if (audio_format.format != SampleFormat::S16 && + audio_format.format != SampleFormat::S24_P32) + audio_format.format = SampleFormat::S24_P32; } static void mpd_jack_error(const char *msg) { - g_warning("%s", msg); + LogError(jack_output_domain, msg); } #ifdef HAVE_JACK_SET_INFO_FUNCTION static void mpd_jack_info(const char *msg) { - g_message("%s", msg); + LogInfo(jack_output_domain, msg); } #endif @@ -220,14 +229,14 @@ mpd_jack_info(const char *msg) * Disconnect the JACK client. */ static void -mpd_jack_disconnect(struct jack_data *jd) +mpd_jack_disconnect(JackOutput *jd) { - assert(jd != NULL); - assert(jd->client != NULL); + assert(jd != nullptr); + assert(jd->client != nullptr); jack_deactivate(jd->client); jack_client_close(jd->client); - jd->client = NULL; + jd->client = nullptr; } /** @@ -235,20 +244,20 @@ mpd_jack_disconnect(struct jack_data *jd) * (e.g. register callbacks). */ static bool -mpd_jack_connect(struct jack_data *jd, GError **error_r) +mpd_jack_connect(JackOutput *jd, Error &error) { jack_status_t status; - assert(jd != NULL); + assert(jd != nullptr); jd->shutdown = false; jd->client = jack_client_open(jd->name, jd->options, &status, jd->server_name); - if (jd->client == NULL) { - g_set_error(error_r, jack_output_quark(), 0, - "Failed to connect to JACK server, status=%d", - status); + if (jd->client == nullptr) { + error.Format(jack_output_domain, status, + "Failed to connect to JACK server, status=%d", + status); return false; } @@ -260,10 +269,10 @@ mpd_jack_connect(struct jack_data *jd, GError **error_r) jd->source_ports[i], JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - if (jd->ports[i] == NULL) { - g_set_error(error_r, jack_output_quark(), 0, - "Cannot register output port \"%s\"", - jd->source_ports[i]); + if (jd->ports[i] == nullptr) { + error.Format(jack_output_domain, + "Cannot register output port \"%s\"", + jd->source_ports[i]); mpd_jack_disconnect(jd); return false; } @@ -279,16 +288,15 @@ mpd_jack_test_default_device(void) } static unsigned -parse_port_list(int line, const char *source, char **dest, GError **error_r) +parse_port_list(const char *source, char **dest, Error &error) { char **list = g_strsplit(source, ",", 0); unsigned n = 0; - for (n = 0; list[n] != NULL; ++n) { + for (n = 0; list[n] != nullptr; ++n) { if (n >= MAX_PORTS) { - g_set_error(error_r, jack_output_quark(), 0, - "too many port names in line %d", - line); + error.Set(config_domain, + "too many port names"); return 0; } @@ -298,9 +306,8 @@ parse_port_list(int line, const char *source, char **dest, GError **error_r) g_free(list); if (n == 0) { - g_set_error(error_r, jack_output_quark(), 0, - "at least one port name expected in line %d", - line); + error.Format(config_domain, + "at least one port name expected"); return 0; } @@ -308,72 +315,73 @@ parse_port_list(int line, const char *source, char **dest, GError **error_r) } static struct audio_output * -mpd_jack_init(const struct config_param *param, GError **error_r) +mpd_jack_init(const config_param ¶m, Error &error) { - struct jack_data *jd = g_new(struct jack_data, 1); + JackOutput *jd = new JackOutput(); - if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) { - g_free(jd); - return NULL; + if (!jd->Initialize(param, error)) { + delete jd; + return nullptr; } const char *value; jd->options = JackNullOption; - jd->name = config_get_block_string(param, "client_name", NULL); - if (jd->name != NULL) - jd->options |= JackUseExactName; + jd->name = param.GetBlockValue("client_name", nullptr); + if (jd->name != nullptr) + jd->options = jack_options_t(jd->options | JackUseExactName); else /* if there's a no configured client name, we don't care about the JackUseExactName option */ jd->name = "Music Player Daemon"; - jd->server_name = config_get_block_string(param, "server_name", NULL); - if (jd->server_name != NULL) - jd->options |= JackServerName; + jd->server_name = param.GetBlockValue("server_name", nullptr); + if (jd->server_name != nullptr) + jd->options = jack_options_t(jd->options | JackServerName); - if (!config_get_block_bool(param, "autostart", false)) - jd->options |= JackNoStartServer; + if (!param.GetBlockValue("autostart", false)) + jd->options = jack_options_t(jd->options | JackNoStartServer); /* configure the source ports */ - value = config_get_block_string(param, "source_ports", "left,right"); - jd->num_source_ports = parse_port_list(param->line, value, - jd->source_ports, error_r); + value = param.GetBlockValue("source_ports", "left,right"); + jd->num_source_ports = parse_port_list(value, + jd->source_ports, error); if (jd->num_source_ports == 0) - return NULL; + return nullptr; /* configure the destination ports */ - value = config_get_block_string(param, "destination_ports", NULL); - if (value == NULL) { + value = param.GetBlockValue("destination_ports", nullptr); + if (value == nullptr) { /* compatibility with MPD < 0.16 */ - value = config_get_block_string(param, "ports", NULL); - if (value != NULL) - g_warning("deprecated option 'ports' in line %d", - param->line); + value = param.GetBlockValue("ports", nullptr); + if (value != nullptr) + FormatWarning(jack_output_domain, + "deprecated option 'ports' in line %d", + param.line); } - if (value != NULL) { + if (value != nullptr) { jd->num_destination_ports = - parse_port_list(param->line, value, - jd->destination_ports, error_r); + parse_port_list(value, + jd->destination_ports, error); if (jd->num_destination_ports == 0) - return NULL; + return nullptr; } else { jd->num_destination_ports = 0; } if (jd->num_destination_ports > 0 && jd->num_destination_ports != jd->num_source_ports) - g_warning("number of source ports (%u) mismatches the " - "number of destination ports (%u) in line %d", - jd->num_source_ports, jd->num_destination_ports, - param->line); + FormatWarning(jack_output_domain, + "number of source ports (%u) mismatches the " + "number of destination ports (%u) in line %d", + jd->num_source_ports, jd->num_destination_ports, + param.line); - jd->ringbuffer_size = - config_get_block_unsigned(param, "ringbuffer_size", 32768); + jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); jack_set_error_function(mpd_jack_error); @@ -387,7 +395,7 @@ mpd_jack_init(const struct config_param *param, GError **error_r) static void mpd_jack_finish(struct audio_output *ao) { - struct jack_data *jd = (struct jack_data *)ao; + JackOutput *jd = (JackOutput *)ao; for (unsigned i = 0; i < jd->num_source_ports; ++i) g_free(jd->source_ports[i]); @@ -395,33 +403,33 @@ mpd_jack_finish(struct audio_output *ao) for (unsigned i = 0; i < jd->num_destination_ports; ++i) g_free(jd->destination_ports[i]); - ao_base_finish(&jd->base); - g_free(jd); + jd->Deinitialize(); + delete jd; } static bool -mpd_jack_enable(struct audio_output *ao, GError **error_r) +mpd_jack_enable(struct audio_output *ao, Error &error) { - struct jack_data *jd = (struct jack_data *)ao; + JackOutput *jd = (JackOutput *)ao; for (unsigned i = 0; i < jd->num_source_ports; ++i) - jd->ringbuffer[i] = NULL; + jd->ringbuffer[i] = nullptr; - return mpd_jack_connect(jd, error_r); + return mpd_jack_connect(jd, error); } static void mpd_jack_disable(struct audio_output *ao) { - struct jack_data *jd = (struct jack_data *)ao; + JackOutput *jd = (JackOutput *)ao; - if (jd->client != NULL) + if (jd->client != nullptr) mpd_jack_disconnect(jd); for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] != NULL) { + if (jd->ringbuffer[i] != nullptr) { jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = NULL; + jd->ringbuffer[i] = nullptr; } } } @@ -430,11 +438,11 @@ mpd_jack_disable(struct audio_output *ao) * Stops the playback on the JACK connection. */ static void -mpd_jack_stop(struct jack_data *jd) +mpd_jack_stop(JackOutput *jd) { - assert(jd != NULL); + assert(jd != nullptr); - if (jd->client == NULL) + if (jd->client == nullptr) return; if (jd->shutdown) @@ -446,13 +454,13 @@ mpd_jack_stop(struct jack_data *jd) } static bool -mpd_jack_start(struct jack_data *jd, GError **error_r) +mpd_jack_start(JackOutput *jd, Error &error) { const char *destination_ports[MAX_PORTS], **jports; - const char *duplicate_port = NULL; + const char *duplicate_port = nullptr; unsigned num_destination_ports; - assert(jd->client != NULL); + assert(jd->client != nullptr); assert(jd->audio_format.channels <= jd->num_source_ports); /* allocate the ring buffers on the first open(); these @@ -460,7 +468,7 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) because we can never know when mpd_jack_process() gets called */ for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] == NULL) + if (jd->ringbuffer[i] == nullptr) jd->ringbuffer[i] = jack_ringbuffer_create(jd->ringbuffer_size); @@ -470,8 +478,7 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) } if ( jack_activate(jd->client) ) { - g_set_error(error_r, jack_output_quark(), 0, - "cannot activate client"); + error.Set(jack_output_domain, "cannot activate client"); mpd_jack_stop(jd); return false; } @@ -479,24 +486,24 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) if (jd->num_destination_ports == 0) { /* no output ports were configured - ask libjack for defaults */ - jports = jack_get_ports(jd->client, NULL, NULL, + jports = jack_get_ports(jd->client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); - if (jports == NULL) { - g_set_error(error_r, jack_output_quark(), 0, - "no ports found"); + if (jports == nullptr) { + error.Set(jack_output_domain, "no ports found"); mpd_jack_stop(jd); return false; } - assert(*jports != NULL); + assert(*jports != nullptr); for (num_destination_ports = 0; num_destination_ports < MAX_PORTS && - jports[num_destination_ports] != NULL; + jports[num_destination_ports] != nullptr; ++num_destination_ports) { - g_debug("destination_port[%u] = '%s'\n", - num_destination_ports, - jports[num_destination_ports]); + FormatDebug(jack_output_domain, + "destination_port[%u] = '%s'\n", + num_destination_ports, + jports[num_destination_ports]); destination_ports[num_destination_ports] = jports[num_destination_ports]; } @@ -507,7 +514,7 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) memcpy(destination_ports, jd->destination_ports, num_destination_ports * sizeof(*destination_ports)); - jports = NULL; + jports = nullptr; } assert(num_destination_ports > 0); @@ -537,11 +544,11 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), destination_ports[i]); if (ret != 0) { - g_set_error(error_r, jack_output_quark(), 0, - "Not a valid JACK port: %s", - destination_ports[i]); + error.Format(jack_output_domain, + "Not a valid JACK port: %s", + destination_ports[i]); - if (jports != NULL) + if (jports != nullptr) free(jports); mpd_jack_stop(jd); @@ -549,7 +556,7 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) } } - if (duplicate_port != NULL) { + if (duplicate_port != nullptr) { /* mono input file: connect the one source channel to the both destination channels */ int ret; @@ -557,11 +564,11 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), duplicate_port); if (ret != 0) { - g_set_error(error_r, jack_output_quark(), 0, - "Not a valid JACK port: %s", - duplicate_port); + error.Format(jack_output_domain, + "Not a valid JACK port: %s", + duplicate_port); - if (jports != NULL) + if (jports != nullptr) free(jports); mpd_jack_stop(jd); @@ -569,41 +576,41 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) } } - if (jports != NULL) + if (jports != nullptr) free(jports); return true; } static bool -mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) +mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { - struct jack_data *jd = (struct jack_data *)ao; + JackOutput *jd = (JackOutput *)ao; - assert(jd != NULL); + assert(jd != nullptr); jd->pause = false; - if (jd->client != NULL && jd->shutdown) + if (jd->client != nullptr && jd->shutdown) mpd_jack_disconnect(jd); - if (jd->client == NULL && !mpd_jack_connect(jd, error_r)) + if (jd->client == nullptr && !mpd_jack_connect(jd, error)) return false; set_audioformat(jd, audio_format); - jd->audio_format = *audio_format; + jd->audio_format = audio_format; - if (!mpd_jack_start(jd, error_r)) + if (!mpd_jack_start(jd, error)) return false; return true; } static void -mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao) +mpd_jack_close(gcc_unused struct audio_output *ao) { - struct jack_data *jd = (struct jack_data *)ao; + JackOutput *jd = (JackOutput *)ao; mpd_jack_stop(jd); } @@ -611,7 +618,7 @@ mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao) static unsigned mpd_jack_delay(struct audio_output *ao) { - struct jack_data *jd = (struct jack_data *)ao; + JackOutput *jd = (JackOutput *)ao; return jd->base.pause && jd->pause && !jd->shutdown ? 1000 @@ -625,7 +632,7 @@ sample_16_to_jack(int16_t sample) } static void -mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, +mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src, unsigned num_samples) { jack_default_audio_sample_t sample; @@ -634,7 +641,8 @@ mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, while (num_samples-- > 0) { for (i = 0; i < jd->audio_format.channels; ++i) { sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + jack_ringbuffer_write(jd->ringbuffer[i], + (const char *)&sample, sizeof(sample)); } } @@ -647,7 +655,7 @@ sample_24_to_jack(int32_t sample) } static void -mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, +mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src, unsigned num_samples) { jack_default_audio_sample_t sample; @@ -656,38 +664,40 @@ mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, while (num_samples-- > 0) { for (i = 0; i < jd->audio_format.channels; ++i) { sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + jack_ringbuffer_write(jd->ringbuffer[i], + (const char *)&sample, sizeof(sample)); } } } static void -mpd_jack_write_samples(struct jack_data *jd, const void *src, +mpd_jack_write_samples(JackOutput *jd, const void *src, unsigned num_samples) { switch (jd->audio_format.format) { - case SAMPLE_FORMAT_S16: + case SampleFormat::S16: mpd_jack_write_samples_16(jd, (const int16_t*)src, num_samples); break; - case SAMPLE_FORMAT_S24_P32: + case SampleFormat::S24_P32: mpd_jack_write_samples_24(jd, (const int32_t*)src, num_samples); break; default: assert(false); + gcc_unreachable(); } } static size_t mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) + Error &error) { - struct jack_data *jd = (struct jack_data *)ao; - const size_t frame_size = audio_format_frame_size(&jd->audio_format); + JackOutput *jd = (JackOutput *)ao; + const size_t frame_size = jd->audio_format.GetFrameSize(); size_t space = 0, space1; jd->pause = false; @@ -697,9 +707,9 @@ mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, while (true) { if (jd->shutdown) { - g_set_error(error_r, jack_output_quark(), 0, - "Refusing to play, because " - "there is no client thread"); + error.Set(jack_output_domain, + "Refusing to play, because " + "there is no client thread"); return 0; } @@ -730,7 +740,7 @@ mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, static bool mpd_jack_pause(struct audio_output *ao) { - struct jack_data *jd = (struct jack_data *)ao; + JackOutput *jd = (JackOutput *)ao; if (jd->shutdown) return false; @@ -741,15 +751,19 @@ mpd_jack_pause(struct audio_output *ao) } const struct audio_output_plugin jack_output_plugin = { - .name = "jack", - .test_default_device = mpd_jack_test_default_device, - .init = mpd_jack_init, - .finish = mpd_jack_finish, - .enable = mpd_jack_enable, - .disable = mpd_jack_disable, - .open = mpd_jack_open, - .delay = mpd_jack_delay, - .play = mpd_jack_play, - .pause = mpd_jack_pause, - .close = mpd_jack_close, + "jack", + mpd_jack_test_default_device, + mpd_jack_init, + mpd_jack_finish, + mpd_jack_enable, + mpd_jack_disable, + mpd_jack_open, + mpd_jack_close, + mpd_jack_delay, + nullptr, + mpd_jack_play, + nullptr, + nullptr, + mpd_jack_pause, + nullptr, }; diff --git a/src/output/jack_output_plugin.h b/src/output/JackOutputPlugin.hxx index 2f94ae7dc..908105ad2 100644 --- a/src/output/jack_output_plugin.h +++ b/src/output/JackOutputPlugin.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_JACK_OUTPUT_PLUGIN_H -#define MPD_JACK_OUTPUT_PLUGIN_H +#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX +#define MPD_JACK_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin jack_output_plugin; diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx new file mode 100644 index 000000000..e2eec9dbc --- /dev/null +++ b/src/output/NullOutputPlugin.cxx @@ -0,0 +1,143 @@ +/* + * 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 "NullOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "Timer.hxx" + +#include <assert.h> + +struct NullOutput { + struct audio_output base; + + bool sync; + + Timer *timer; + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &null_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; + +static struct audio_output * +null_init(const config_param ¶m, Error &error) +{ + NullOutput *nd = new NullOutput(); + + if (!nd->Initialize(param, error)) { + delete nd; + return nullptr; + } + + nd->sync = param.GetBlockValue("sync", true); + + return &nd->base; +} + +static void +null_finish(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + nd->Deinitialize(); + delete nd; +} + +static bool +null_open(struct audio_output *ao, AudioFormat &audio_format, + gcc_unused Error &error) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + nd->timer = new Timer(audio_format); + + return true; +} + +static void +null_close(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + delete nd->timer; +} + +static unsigned +null_delay(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + return nd->sync && nd->timer->IsStarted() + ? nd->timer->GetDelay() + : 0; +} + +static size_t +null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size, + gcc_unused Error &error) +{ + NullOutput *nd = (NullOutput *)ao; + Timer *timer = nd->timer; + + if (!nd->sync) + return size; + + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); + + return size; +} + +static void +null_cancel(struct audio_output *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (!nd->sync) + return; + + nd->timer->Reset(); +} + +const struct audio_output_plugin null_output_plugin = { + "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..eee215b32 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,19 +18,21 @@ */ #include "config.h" -#include "osx_output_plugin.h" -#include "output_api.h" -#include "fifo_buffer.h" +#include "OSXOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "util/fifo_buffer.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "Log.hxx" #include <glib.h> #include <CoreAudio/AudioHardware.h> #include <AudioUnit/AudioUnit.h> #include <CoreServices/CoreServices.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "osx" - -struct osx_output { +struct OSXOutput { struct audio_output base; /* configuration settings */ @@ -39,20 +41,13 @@ struct osx_output { const char *device_name; AudioUnit au; - GMutex *mutex; - GCond *condition; + Mutex mutex; + Cond condition; struct fifo_buffer *buffer; }; -/** - * The quark used for GError.domain. - */ -static inline GQuark -osx_output_quark(void) -{ - return g_quark_from_static_string("osx_output"); -} +static constexpr Domain osx_output_domain("osx_output"); static bool osx_output_test_default_device(void) @@ -63,9 +58,9 @@ 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 config_param ¶m) { - const char *device = config_get_block_string(param, "device", NULL); + const char *device = param.GetBlockValue("device"); if (device == NULL || 0 == strcmp(device, "default")) { oo->component_subtype = kAudioUnitSubType_DefaultOutput; @@ -83,17 +78,15 @@ 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) +osx_output_init(const config_param ¶m, Error &error) { - struct osx_output *oo = g_new(struct osx_output, 1); - if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { - g_free(oo); + OSXOutput *oo = new OSXOutput(); + if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) { + delete oo; return NULL; } osx_output_configure(oo, param); - oo->mutex = g_mutex_new(); - oo->condition = g_cond_new(); return &oo->base; } @@ -101,15 +94,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, Error &error) { bool ret = true; OSStatus status; @@ -126,23 +117,23 @@ osx_output_set_device(struct osx_output *oo, GError **error) &size, NULL); if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine number of OS X audio devices: %s", - GetMacOSStatusCommentString(status)); + error.Format(osx_output_domain, status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); ret = false; goto done; } /* what are the available audio device IDs? */ numdevices = size / sizeof(AudioDeviceID); - deviceids = g_malloc(size); + deviceids = new AudioDeviceID[numdevices]; status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, deviceids); if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine OS X audio device IDs: %s", - GetMacOSStatusCommentString(status)); + error.Format(osx_output_domain, status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); ret = false; goto done; } @@ -154,24 +145,26 @@ osx_output_set_device(struct osx_output *oo, GError **error) kAudioDevicePropertyDeviceName, &size, name); if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to determine OS X device name " - "(device %u): %s", - (unsigned int) deviceids[i], - GetMacOSStatusCommentString(status)); + error.Format(osx_output_domain, status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); ret = false; goto done; } if (strcmp(oo->device_name, name) == 0) { - g_debug("found matching device: ID=%u, name=%s", - (unsigned int) deviceids[i], name); + FormatDebug(osx_output_domain, + "found matching device: ID=%u, name=%s", + (unsigned)deviceids[i], name); break; } } if (i == numdevices) { - g_warning("Found no audio device with name '%s' " - "(will use default audio device)", - oo->device_name); + FormatWarning(osx_output_domain, + "Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); goto done; } @@ -182,36 +175,37 @@ osx_output_set_device(struct osx_output *oo, GError **error) &(deviceids[i]), sizeof(AudioDeviceID)); if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to set OS X audio output device: %s", - GetMacOSStatusCommentString(status)); + error.Format(osx_output_domain, status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); ret = false; goto done; } - g_debug("set OS X audio output device ID=%u, name=%s", - (unsigned int) deviceids[i], name); + + FormatDebug(osx_output_domain, + "set OS X audio output device ID=%u, name=%s", + (unsigned)deviceids[i], name); done: - if (deviceids != NULL) - g_free(deviceids); + delete[] deviceids; return ret; } static OSStatus osx_render(void *vdata, - G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags, - G_GNUC_UNUSED const AudioTimeStamp *in_timestamp, - G_GNUC_UNUSED UInt32 in_bus_number, - G_GNUC_UNUSED UInt32 in_number_frames, + gcc_unused AudioUnitRenderActionFlags *io_action_flags, + gcc_unused const AudioTimeStamp *in_timestamp, + gcc_unused UInt32 in_bus_number, + gcc_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 +219,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; @@ -240,9 +234,9 @@ osx_render(void *vdata, } static bool -osx_output_enable(struct audio_output *ao, GError **error_r) +osx_output_enable(struct audio_output *ao, Error &error) { - struct osx_output *oo = (struct osx_output *)ao; + OSXOutput *oo = (OSXOutput *)ao; ComponentDescription desc; desc.componentType = kAudioUnitType_Output; @@ -253,20 +247,20 @@ osx_output_enable(struct audio_output *ao, GError **error_r) Component comp = FindNextComponent(NULL, &desc); if (comp == 0) { - g_set_error(error_r, osx_output_quark(), 0, - "Error finding OS X component"); + error.Set(osx_output_domain, + "Error finding OS X component"); return false; } OSStatus status = OpenAComponent(comp, &oo->au); if (status != noErr) { - g_set_error(error_r, osx_output_quark(), status, - "Unable to open OS X component: %s", - GetMacOSStatusCommentString(status)); + error.Format(osx_output_domain, status, + "Unable to open OS X component: %s", + GetMacOSStatusCommentString(status)); return false; } - if (!osx_output_set_device(oo, error_r)) { + if (!osx_output_set_device(oo, error)) { CloseComponent(oo->au); return false; } @@ -282,8 +276,8 @@ osx_output_enable(struct audio_output *ao, GError **error_r) &callback, sizeof(callback)); if (result != noErr) { CloseComponent(oo->au); - g_set_error(error_r, osx_output_quark(), result, - "unable to set callback for OS X audio unit"); + error.Set(osx_output_domain, result, + "unable to set callback for OS X audio unit"); return false; } @@ -293,7 +287,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 +295,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); @@ -320,30 +313,31 @@ osx_output_close(struct audio_output *ao) } static bool -osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +osx_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { - struct osx_output *od = (struct osx_output *)ao; + OSXOutput *od = (OSXOutput *)ao; AudioStreamBasicDescription stream_description; - stream_description.mSampleRate = audio_format->sample_rate; + stream_description.mSampleRate = audio_format.sample_rate; stream_description.mFormatID = kAudioFormatLinearPCM; stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - switch (audio_format->format) { - case SAMPLE_FORMAT_S8: + switch (audio_format.format) { + case SampleFormat::S8: stream_description.mBitsPerChannel = 8; break; - case SAMPLE_FORMAT_S16: + case SampleFormat::S16: stream_description.mBitsPerChannel = 16; break; - case SAMPLE_FORMAT_S32: + case SampleFormat::S32: stream_description.mBitsPerChannel = 32; break; default: - audio_format->format = SAMPLE_FORMAT_S32; + audio_format.format = SampleFormat::S32; stream_description.mBitsPerChannel = 32; break; } @@ -352,11 +346,10 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; #endif - stream_description.mBytesPerPacket = - audio_format_frame_size(audio_format); + stream_description.mBytesPerPacket = audio_format.GetFrameSize(); stream_description.mFramesPerPacket = 1; stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; - stream_description.mChannelsPerFrame = audio_format->channels; + stream_description.mChannelsPerFrame = audio_format.channels; ComponentResult result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, @@ -364,29 +357,29 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr &stream_description, sizeof(stream_description)); if (result != noErr) { - g_set_error(error, osx_output_quark(), result, - "Unable to set format on OS X device"); + error.Set(osx_output_domain, result, + "Unable to set format on OS X device"); return false; } OSStatus status = AudioUnitInitialize(od->au); if (status != noErr) { - g_set_error(error, osx_output_quark(), status, - "Unable to initialize OS X audio unit: %s", - GetMacOSStatusCommentString(status)); + error.Set(osx_output_domain, status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); return false; } /* create a buffer of 1s */ - od->buffer = fifo_buffer_new(audio_format->sample_rate * - audio_format_frame_size(audio_format)); + od->buffer = fifo_buffer_new(audio_format.sample_rate * + audio_format.GetFrameSize()); status = AudioOutputUnitStart(od->au); if (status != 0) { AudioUnitUninitialize(od->au); - g_set_error(error, osx_output_quark(), status, - "unable to start audio output: %s", - GetMacOSStatusCommentString(status)); + error.Format(osx_output_domain, status, + "unable to start audio output: %s", + GetMacOSStatusCommentString(status)); return false; } @@ -395,11 +388,11 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr static size_t osx_output_play(struct audio_output *ao, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) + gcc_unused Error &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 +403,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 +412,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/openal_output_plugin.c b/src/output/OpenALOutputPlugin.cxx index ebd35ef12..e753b206f 100644 --- a/src/output/openal_output_plugin.c +++ b/src/output/OpenALOutputPlugin.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,8 +18,10 @@ */ #include "config.h" -#include "openal_output_plugin.h" -#include "output_api.h" +#include "OpenALOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" #include <glib.h> @@ -31,13 +33,10 @@ #include <OpenAL/alc.h> #endif -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "openal" - /* should be enough for buffer size = 2048 */ #define NUM_BUFFERS 16 -struct openal_data { +struct OpenALOutput { struct audio_output base; const char *device_name; @@ -48,81 +47,85 @@ struct openal_data { ALuint source; ALenum format; ALuint frequency; + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &openal_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } }; -static inline GQuark -openal_output_quark(void) -{ - return g_quark_from_static_string("openal_output"); -} +static constexpr Domain openal_output_domain("openal_output"); static ALenum -openal_audio_format(struct audio_format *audio_format) +openal_audio_format(AudioFormat &audio_format) { - /* note: cannot map SAMPLE_FORMAT_S8 to AL_FORMAT_STEREO8 or + /* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit samples, while MPD uses signed samples */ - switch (audio_format->format) { - case SAMPLE_FORMAT_S16: - if (audio_format->channels == 2) + switch (audio_format.format) { + case SampleFormat::S16: + if (audio_format.channels == 2) return AL_FORMAT_STEREO16; - if (audio_format->channels == 1) + if (audio_format.channels == 1) return AL_FORMAT_MONO16; /* fall back to mono */ - audio_format->channels = 1; + audio_format.channels = 1; return openal_audio_format(audio_format); default: /* fall back to 16 bit */ - audio_format->format = SAMPLE_FORMAT_S16; + audio_format.format = SampleFormat::S16; return openal_audio_format(audio_format); } } -G_GNUC_PURE +gcc_pure static inline ALint -openal_get_source_i(const struct openal_data *od, ALenum param) +openal_get_source_i(const OpenALOutput *od, ALenum param) { ALint value; alGetSourcei(od->source, param, &value); return value; } -G_GNUC_PURE +gcc_pure static inline bool -openal_has_processed(const struct openal_data *od) +openal_has_processed(const OpenALOutput *od) { return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; } -G_GNUC_PURE +gcc_pure static inline ALint -openal_is_playing(const struct openal_data *od) +openal_is_playing(const OpenALOutput *od) { return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; } static bool -openal_setup_context(struct openal_data *od, - GError **error) +openal_setup_context(OpenALOutput *od, Error &error) { od->device = alcOpenDevice(od->device_name); - if (od->device == NULL) { - g_set_error(error, openal_output_quark(), 0, - "Error opening OpenAL device \"%s\"\n", - od->device_name); + if (od->device == nullptr) { + error.Format(openal_output_domain, + "Error opening OpenAL device \"%s\"", + od->device_name); return false; } - od->context = alcCreateContext(od->device, NULL); + od->context = alcCreateContext(od->device, nullptr); - if (od->context == NULL) { - g_set_error(error, openal_output_quark(), 0, - "Error creating context for \"%s\"\n", - od->device_name); + if (od->context == nullptr) { + error.Format(openal_output_domain, + "Error creating context for \"%s\"", + od->device_name); alcCloseDevice(od->device); return false; } @@ -131,19 +134,17 @@ openal_setup_context(struct openal_data *od, } static struct audio_output * -openal_init(const struct config_param *param, GError **error_r) +openal_init(const config_param ¶m, Error &error) { - const char *device_name = config_get_block_string(param, "device", NULL); - struct openal_data *od; - - if (device_name == NULL) { - device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + const char *device_name = param.GetBlockValue("device"); + if (device_name == nullptr) { + device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); } - od = g_new(struct openal_data, 1); - if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) { - g_free(od); - return NULL; + OpenALOutput *od = new OpenALOutput(); + if (!od->Initialize(param, error)) { + delete od; + return nullptr; } od->device_name = device_name; @@ -154,17 +155,17 @@ openal_init(const struct config_param *param, GError **error_r) static void openal_finish(struct audio_output *ao) { - struct openal_data *od = (struct openal_data *)ao; + OpenALOutput *od = (OpenALOutput *)ao; - ao_base_finish(&od->base); - g_free(od); + od->Deinitialize(); + delete od; } static bool -openal_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) +openal_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { - struct openal_data *od = (struct openal_data *)ao; + OpenALOutput *od = (OpenALOutput *)ao; od->format = openal_audio_format(audio_format); @@ -176,22 +177,20 @@ openal_open(struct audio_output *ao, struct audio_format *audio_format, alGenBuffers(NUM_BUFFERS, od->buffers); if (alGetError() != AL_NO_ERROR) { - g_set_error(error, openal_output_quark(), 0, - "Failed to generate buffers"); + error.Set(openal_output_domain, "Failed to generate buffers"); return false; } alGenSources(1, &od->source); if (alGetError() != AL_NO_ERROR) { - g_set_error(error, openal_output_quark(), 0, - "Failed to generate source"); + error.Set(openal_output_domain, "Failed to generate source"); alDeleteBuffers(NUM_BUFFERS, od->buffers); return false; } od->filled = 0; - od->frequency = audio_format->sample_rate; + od->frequency = audio_format.sample_rate; return true; } @@ -199,7 +198,7 @@ openal_open(struct audio_output *ao, struct audio_format *audio_format, static void openal_close(struct audio_output *ao) { - struct openal_data *od = (struct openal_data *)ao; + OpenALOutput *od = (OpenALOutput *)ao; alcMakeContextCurrent(od->context); alDeleteSources(1, &od->source); @@ -211,7 +210,7 @@ openal_close(struct audio_output *ao) static unsigned openal_delay(struct audio_output *ao) { - struct openal_data *od = (struct openal_data *)ao; + OpenALOutput *od = (OpenALOutput *)ao; return od->filled < NUM_BUFFERS || openal_has_processed(od) ? 0 @@ -223,9 +222,9 @@ openal_delay(struct audio_output *ao) static size_t openal_play(struct audio_output *ao, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) + gcc_unused Error &error) { - struct openal_data *od = (struct openal_data *)ao; + OpenALOutput *od = (OpenALOutput *)ao; ALuint buffer; if (alcGetCurrentContext() != od->context) { @@ -256,7 +255,7 @@ openal_play(struct audio_output *ao, const void *chunk, size_t size, static void openal_cancel(struct audio_output *ao) { - struct openal_data *od = (struct openal_data *)ao; + OpenALOutput *od = (OpenALOutput *)ao; od->filled = 0; alcMakeContextCurrent(od->context); @@ -268,12 +267,19 @@ openal_cancel(struct audio_output *ao) } const struct audio_output_plugin openal_output_plugin = { - .name = "openal", - .init = openal_init, - .finish = openal_finish, - .open = openal_open, - .close = openal_close, - .delay = openal_delay, - .play = openal_play, - .cancel = openal_cancel, + "openal", + nullptr, + openal_init, + openal_finish, + nullptr, + nullptr, + openal_open, + openal_close, + openal_delay, + nullptr, + openal_play, + nullptr, + openal_cancel, + nullptr, + nullptr, }; diff --git a/src/output/openal_output_plugin.h b/src/output/OpenALOutputPlugin.hxx index 25f6ccf46..e1ebf3d4f 100644 --- a/src/output/openal_output_plugin.h +++ b/src/output/OpenALOutputPlugin.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_OPENAL_OUTPUT_PLUGIN_H -#define MPD_OPENAL_OUTPUT_PLUGIN_H +#ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX +#define MPD_OPENAL_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin openal_output_plugin; diff --git a/src/output/oss_output_plugin.c b/src/output/OssOutputPlugin.cxx index e366a4537..781e2bf43 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,13 @@ */ #include "config.h" -#include "oss_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "fd_util.h" -#include "glib_compat.h" +#include "OssOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" #include <glib.h> @@ -34,9 +36,6 @@ #include <unistd.h> #include <assert.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "oss" - #if defined(__OpenBSD__) || defined(__NetBSD__) # include <soundcard.h> #else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ @@ -53,14 +52,15 @@ #endif #ifdef AFMT_S24_PACKED -#include "pcm_export.h" +#include "pcm/PcmExport.hxx" +#include "util/Manual.hxx" #endif -struct oss_data { +struct OssOutput { struct audio_output base; #ifdef AFMT_S24_PACKED - struct pcm_export_state export; + Manual<PcmExport> pcm_export; #endif int fd; @@ -70,40 +70,27 @@ struct oss_data { * The current input audio format. This is needed to reopen * the device after cancel(). */ - struct audio_format audio_format; + AudioFormat audio_format; /** * The current OSS audio format. This is needed to reopen the * device after cancel(). */ int oss_format; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -oss_output_quark(void) -{ - return g_quark_from_static_string("oss_output"); -} -static struct oss_data * -oss_data_new(void) -{ - struct oss_data *ret = g_new(struct oss_data, 1); + OssOutput():fd(-1), device(nullptr) {} - ret->device = NULL; - ret->fd = -1; + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &oss_output_plugin, param, + error_r); + } - return ret; -} + void Deinitialize() { + ao_base_finish(&base); + } +}; -static void -oss_data_free(struct oss_data *od) -{ - g_free(od); -} +static constexpr Domain oss_output_domain("oss_output"); enum oss_stat { OSS_STAT_NO_ERROR = 0, @@ -153,27 +140,28 @@ oss_output_test_default_device(void) close(fd); return true; } - g_warning("Error opening OSS device \"%s\": %s\n", - default_devices[i], g_strerror(errno)); + + FormatErrno(oss_output_domain, + "Error opening OSS device \"%s\"", + default_devices[i]); } return false; } static struct audio_output * -oss_open_default(GError **error) +oss_open_default(Error &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; ) { + const config_param empty; + 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(); - if (!ao_base_init(&od->base, &oss_output_plugin, NULL, - error)) { - g_free(od); + OssOutput *od = new OssOutput(); + if (!od->Initialize(empty, error)) { + delete od; return NULL; } @@ -182,41 +170,43 @@ 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: /* never reached */ break; case OSS_STAT_DOESN_T_EXIST: - g_warning("%s not found\n", dev); + FormatWarning(oss_output_domain, + "%s not found", dev); break; case OSS_STAT_NOT_CHAR_DEV: - g_warning("%s is not a character device\n", dev); + FormatWarning(oss_output_domain, + "%s is not a character device", dev); break; case OSS_STAT_NO_PERMS: - g_warning("%s: permission denied\n", dev); + FormatWarning(oss_output_domain, + "%s: permission denied", dev); break; case OSS_STAT_OTHER: - g_warning("Error accessing %s: %s\n", - dev, g_strerror(err[i])); + FormatErrno(oss_output_domain, err[i], + "Error accessing %s", dev); } } - g_set_error(error, oss_output_quark(), 0, - "error trying to open default OSS device"); + error.Set(oss_output_domain, + "error trying to open default OSS device"); return NULL; } static struct audio_output * -oss_output_init(const struct config_param *param, GError **error) +oss_output_init(const config_param ¶m, Error &error) { - const char *device = config_get_block_string(param, "device", NULL); + const char *device = param.GetBlockValue("device"); if (device != NULL) { - struct oss_data *od = oss_data_new(); - if (!ao_base_init(&od->base, &oss_output_plugin, param, - error)) { - g_free(od); + OssOutput *od = new OssOutput(); + if (!od->Initialize(param, error)) { + delete od; return NULL; } @@ -230,35 +220,35 @@ oss_output_init(const struct config_param *param, GError **error) static void oss_output_finish(struct audio_output *ao) { - struct oss_data *od = (struct oss_data *)ao; + OssOutput *od = (OssOutput *)ao; ao_base_finish(&od->base); - oss_data_free(od); + delete od; } #ifdef AFMT_S24_PACKED static bool -oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +oss_output_enable(struct audio_output *ao, gcc_unused Error &error) { - struct oss_data *od = (struct oss_data *)ao; + OssOutput *od = (OssOutput *)ao; - pcm_export_init(&od->export); + od->pcm_export.Construct(); return true; } static void oss_output_disable(struct audio_output *ao) { - struct oss_data *od = (struct oss_data *)ao; + OssOutput *od = (OssOutput *)ao; - pcm_export_deinit(&od->export); + od->pcm_export.Destruct(); } #endif static void -oss_close(struct oss_data *od) +oss_close(OssOutput *od) { if (od->fd >= 0) close(od->fd); @@ -277,16 +267,16 @@ enum oss_setup_result { /** * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates a GError. + * returned. Any other failure returns ERROR and allocates an #Error. */ static enum oss_setup_result oss_try_ioctl_r(int fd, unsigned long request, int *value_r, - const char *msg, GError **error_r) + const char *msg, Error &error) { assert(fd >= 0); assert(value_r != NULL); assert(msg != NULL); - assert(error_r == NULL || *error_r == NULL); + assert(!error.IsDefined()); int ret = ioctl(fd, request, value_r); if (ret >= 0) @@ -295,19 +285,18 @@ oss_try_ioctl_r(int fd, unsigned long request, int *value_r, if (errno == EINVAL) return UNSUPPORTED; - g_set_error(error_r, oss_output_quark(), errno, - "%s: %s", msg, g_strerror(errno)); + error.SetErrno(msg); return ERROR; } /** * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates a GError. + * returned. Any other failure returns ERROR and allocates an #Error. */ static enum oss_setup_result oss_try_ioctl(int fd, unsigned long request, int value, - const char *msg, GError **error_r) + const char *msg, Error &error_r) { return oss_try_ioctl_r(fd, request, &value, msg, error_r); } @@ -317,18 +306,18 @@ oss_try_ioctl(int fd, unsigned long request, int value, * specified number is not supported. */ static bool -oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) +oss_setup_channels(int fd, AudioFormat &audio_format, Error &error) { const char *const msg = "Failed to set channel count"; - int channels = audio_format->channels; + int channels = audio_format.channels; enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); + oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error); switch (result) { case SUCCESS: if (!audio_valid_channel_count(channels)) break; - audio_format->channels = channels; + audio_format.channels = channels; return true; case ERROR: @@ -339,19 +328,19 @@ oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) } for (unsigned i = 1; i < 2; ++i) { - if (i == audio_format->channels) + if (i == audio_format.channels) /* don't try that again */ continue; channels = i; result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, - msg, error_r); + msg, error); switch (result) { case SUCCESS: if (!audio_valid_channel_count(channels)) break; - audio_format->channels = channels; + audio_format.channels = channels; return true; case ERROR: @@ -362,7 +351,7 @@ oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) } } - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + error.Set(oss_output_domain, msg); return false; } @@ -371,20 +360,20 @@ oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) * specified sample rate is not supported. */ static bool -oss_setup_sample_rate(int fd, struct audio_format *audio_format, - GError **error_r) +oss_setup_sample_rate(int fd, AudioFormat &audio_format, + Error &error) { const char *const msg = "Failed to set sample rate"; - int sample_rate = audio_format->sample_rate; + int sample_rate = audio_format.sample_rate; enum oss_setup_result result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error_r); + msg, error); switch (result) { case SUCCESS: if (!audio_valid_sample_rate(sample_rate)) break; - audio_format->sample_rate = sample_rate; + audio_format.sample_rate = sample_rate; return true; case ERROR: @@ -397,17 +386,17 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format, static const int sample_rates[] = { 48000, 44100, 0 }; for (unsigned i = 0; sample_rates[i] != 0; ++i) { sample_rate = sample_rates[i]; - if (sample_rate == (int)audio_format->sample_rate) + if (sample_rate == (int)audio_format.sample_rate) continue; result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error_r); + msg, error); switch (result) { case SUCCESS: if (!audio_valid_sample_rate(sample_rate)) break; - audio_format->sample_rate = sample_rate; + audio_format.sample_rate = sample_rate; return true; case ERROR: @@ -418,7 +407,7 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format, } } - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + error.Set(oss_output_domain, msg); return false; } @@ -427,28 +416,28 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format, * AFMT_QUERY if there is no direct counterpart. */ static int -sample_format_to_oss(enum sample_format format) +sample_format_to_oss(SampleFormat format) { switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: + case SampleFormat::UNDEFINED: + case SampleFormat::FLOAT: + case SampleFormat::DSD: return AFMT_QUERY; - case SAMPLE_FORMAT_S8: + case SampleFormat::S8: return AFMT_S8; - case SAMPLE_FORMAT_S16: + case SampleFormat::S16: return AFMT_S16_NE; - case SAMPLE_FORMAT_S24_P32: + case SampleFormat::S24_P32: #ifdef AFMT_S24_NE return AFMT_S24_NE; #else return AFMT_QUERY; #endif - case SAMPLE_FORMAT_S32: + case SampleFormat::S32: #ifdef AFMT_S32_NE return AFMT_S32_NE; #else @@ -461,52 +450,52 @@ sample_format_to_oss(enum sample_format format) /** * Convert an OSS sample format to its MPD counterpart. Returns - * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. + * SampleFormat::UNDEFINED if there is no direct counterpart. */ -static enum sample_format +static SampleFormat sample_format_from_oss(int format) { switch (format) { case AFMT_S8: - return SAMPLE_FORMAT_S8; + return SampleFormat::S8; case AFMT_S16_NE: - return SAMPLE_FORMAT_S16; + return SampleFormat::S16; #ifdef AFMT_S24_PACKED case AFMT_S24_PACKED: - return SAMPLE_FORMAT_S24_P32; + return SampleFormat::S24_P32; #endif #ifdef AFMT_S24_NE case AFMT_S24_NE: - return SAMPLE_FORMAT_S24_P32; + return SampleFormat::S24_P32; #endif #ifdef AFMT_S32_NE case AFMT_S32_NE: - return SAMPLE_FORMAT_S32; + return SampleFormat::S32; #endif default: - return SAMPLE_FORMAT_UNDEFINED; + return SampleFormat::UNDEFINED; } } /** * Probe one sample format. * - * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on + * @return the selected sample format or SampleFormat::UNDEFINED on * error */ static enum oss_setup_result -oss_probe_sample_format(int fd, enum sample_format sample_format, - enum sample_format *sample_format_r, +oss_probe_sample_format(int fd, SampleFormat sample_format, + SampleFormat *sample_format_r, int *oss_format_r, #ifdef AFMT_S24_PACKED - struct pcm_export_state *export, + PcmExport &pcm_export, #endif - GError **error_r) + Error &error) { int oss_format = sample_format_to_oss(sample_format); if (oss_format == AFMT_QUERY) @@ -515,16 +504,16 @@ oss_probe_sample_format(int fd, enum sample_format sample_format, enum oss_setup_result result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, &oss_format, - "Failed to set sample format", error_r); + "Failed to set sample format", error); #ifdef AFMT_S24_PACKED - if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) { + if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) { /* if the driver doesn't support padded 24 bit, try packed 24 bit */ oss_format = AFMT_S24_PACKED; result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, &oss_format, - "Failed to set sample format", error_r); + "Failed to set sample format", error); } #endif @@ -532,14 +521,14 @@ oss_probe_sample_format(int fd, enum sample_format sample_format, return result; sample_format = sample_format_from_oss(oss_format); - if (sample_format == SAMPLE_FORMAT_UNDEFINED) + if (sample_format == SampleFormat::UNDEFINED) return UNSUPPORTED; *sample_format_r = sample_format; *oss_format_r = oss_format; #ifdef AFMT_S24_PACKED - pcm_export_open(export, sample_format, 0, false, false, + pcm_export.Open(sample_format, 0, false, false, oss_format == AFMT_S24_PACKED, oss_format == AFMT_S24_PACKED && G_BYTE_ORDER != G_LITTLE_ENDIAN); @@ -553,24 +542,24 @@ oss_probe_sample_format(int fd, enum sample_format sample_format, * specified format is not supported. */ static bool -oss_setup_sample_format(int fd, struct audio_format *audio_format, +oss_setup_sample_format(int fd, AudioFormat &audio_format, int *oss_format_r, #ifdef AFMT_S24_PACKED - struct pcm_export_state *export, + PcmExport &pcm_export, #endif - GError **error_r) + Error &error) { - enum sample_format mpd_format; + SampleFormat mpd_format; enum oss_setup_result result = - oss_probe_sample_format(fd, audio_format->format, + oss_probe_sample_format(fd, audio_format.format, &mpd_format, oss_format_r, #ifdef AFMT_S24_PACKED - export, + pcm_export, #endif - error_r); + error); switch (result) { case SUCCESS: - audio_format->format = mpd_format; + audio_format.format = mpd_format; return true; case ERROR: @@ -586,29 +575,29 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format, /* the requested sample format is not available - probe for other formats supported by MPD */ - static const enum sample_format sample_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED /* sentinel */ + static const SampleFormat sample_formats[] = { + SampleFormat::S24_P32, + SampleFormat::S32, + SampleFormat::S16, + SampleFormat::S8, + SampleFormat::UNDEFINED /* sentinel */ }; - for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) { mpd_format = sample_formats[i]; - if (mpd_format == audio_format->format) + if (mpd_format == audio_format.format) /* don't try that again */ continue; result = oss_probe_sample_format(fd, mpd_format, &mpd_format, oss_format_r, #ifdef AFMT_S24_PACKED - export, + pcm_export, #endif - error_r); + error); switch (result) { case SUCCESS: - audio_format->format = mpd_format; + audio_format.format = mpd_format; return true; case ERROR: @@ -619,8 +608,7 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format, } } - g_set_error_literal(error_r, oss_output_quark(), EINVAL, - "Failed to set sample format"); + error.Set(oss_output_domain, "Failed to set sample format"); return false; } @@ -628,31 +616,30 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format, * Sets up the OSS device which was opened before. */ static bool -oss_setup(struct oss_data *od, struct audio_format *audio_format, - GError **error_r) +oss_setup(OssOutput *od, AudioFormat &audio_format, + Error &error) { - return oss_setup_channels(od->fd, audio_format, error_r) && - oss_setup_sample_rate(od->fd, audio_format, error_r) && + return oss_setup_channels(od->fd, audio_format, error) && + oss_setup_sample_rate(od->fd, audio_format, error) && oss_setup_sample_format(od->fd, audio_format, &od->oss_format, #ifdef AFMT_S24_PACKED - &od->export, + od->pcm_export, #endif - error_r); + error); } /** * Reopen the device with the saved audio_format, without any probing. */ static bool -oss_reopen(struct oss_data *od, GError **error_r) +oss_reopen(OssOutput *od, Error &error) { assert(od->fd < 0); od->fd = open_cloexec(od->device, O_WRONLY, 0); if (od->fd < 0) { - g_set_error(error_r, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, g_strerror(errno)); + error.FormatErrno("Error opening OSS device \"%s\"", + od->device); return false; } @@ -660,35 +647,32 @@ oss_reopen(struct oss_data *od, GError **error_r) const char *const msg1 = "Failed to set channel count"; result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, - od->audio_format.channels, msg1, error_r); + od->audio_format.channels, msg1, error); if (result != SUCCESS) { oss_close(od); if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg1); + error.Set(oss_output_domain, msg1); return false; } const char *const msg2 = "Failed to set sample rate"; result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, - od->audio_format.sample_rate, msg2, error_r); + od->audio_format.sample_rate, msg2, error); if (result != SUCCESS) { oss_close(od); if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg2); + error.Set(oss_output_domain, msg2); return false; } const char *const msg3 = "Failed to set sample format"; result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, od->oss_format, - msg3, error_r); + msg3, error); if (result != SUCCESS) { oss_close(od); if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg3); + error.Set(oss_output_domain, msg3); return false; } @@ -696,16 +680,15 @@ oss_reopen(struct oss_data *od, GError **error_r) } static bool -oss_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) +oss_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { - struct oss_data *od = (struct oss_data *)ao; + OssOutput *od = (OssOutput *)ao; od->fd = open_cloexec(od->device, O_WRONLY, 0); if (od->fd < 0) { - g_set_error(error, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, g_strerror(errno)); + error.FormatErrno("Error opening OSS device \"%s\"", + od->device); return false; } @@ -714,14 +697,14 @@ oss_output_open(struct audio_output *ao, struct audio_format *audio_format, return false; } - od->audio_format = *audio_format; + od->audio_format = audio_format; return true; } static void oss_output_close(struct audio_output *ao) { - struct oss_data *od = (struct oss_data *)ao; + OssOutput *od = (OssOutput *)ao; oss_close(od); } @@ -729,7 +712,7 @@ oss_output_close(struct audio_output *ao) static void oss_output_cancel(struct audio_output *ao) { - struct oss_data *od = (struct oss_data *)ao; + OssOutput *od = (OssOutput *)ao; if (od->fd >= 0) { ioctl(od->fd, SNDCTL_DSP_RESET, 0); @@ -739,9 +722,9 @@ oss_output_cancel(struct audio_output *ao) static size_t oss_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) + Error &error) { - struct oss_data *od = (struct oss_data *)ao; + OssOutput *od = (OssOutput *)ao; ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ @@ -749,40 +732,45 @@ 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 = od->pcm_export->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 = od->pcm_export->CalcSourceSize(ret); #endif return ret; } if (ret < 0 && errno != EINTR) { - g_set_error(error, oss_output_quark(), errno, - "Write error on %s: %s", - od->device, g_strerror(errno)); + error.FormatErrno("Write error on %s", od->device); return 0; } } } 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/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx new file mode 100644 index 000000000..2b830ef29 --- /dev/null +++ b/src/output/PipeOutputPlugin.cxx @@ -0,0 +1,149 @@ +/* + * 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 "PipeOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> + +#include <stdio.h> +#include <errno.h> + +struct PipeOutput { + struct audio_output base; + + char *cmd; + FILE *fh; + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &pipe_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain pipe_output_domain("pipe_output"); + +inline bool +PipeOutput::Configure(const config_param ¶m, Error &error) +{ + cmd = param.DupBlockString("command"); + if (cmd == nullptr) { + error.Set(config_domain, + "No \"command\" parameter specified"); + return false; + } + + return true; +} + +static struct audio_output * +pipe_output_init(const config_param ¶m, Error &error) +{ + PipeOutput *pd = new PipeOutput(); + + if (!pd->Initialize(param, error)) { + delete pd; + return nullptr; + } + + if (!pd->Configure(param, error)) { + pd->Deinitialize(); + delete pd; + return nullptr; + } + + return &pd->base; +} + +static void +pipe_output_finish(struct audio_output *ao) +{ + PipeOutput *pd = (PipeOutput *)ao; + + g_free(pd->cmd); + pd->Deinitialize(); + delete pd; +} + +static bool +pipe_output_open(struct audio_output *ao, + gcc_unused AudioFormat &audio_format, + Error &error) +{ + PipeOutput *pd = (PipeOutput *)ao; + + pd->fh = popen(pd->cmd, "w"); + if (pd->fh == nullptr) { + error.FormatErrno("Error opening pipe \"%s\"", + pd->cmd); + return false; + } + + return true; +} + +static void +pipe_output_close(struct audio_output *ao) +{ + PipeOutput *pd = (PipeOutput *)ao; + + pclose(pd->fh); +} + +static size_t +pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + PipeOutput *pd = (PipeOutput *)ao; + size_t ret; + + ret = fwrite(chunk, 1, size, pd->fh); + if (ret == 0) + error.SetErrno("Write error on pipe"); + + return ret; +} + +const struct audio_output_plugin pipe_output_plugin = { + "pipe", + nullptr, + pipe_output_init, + pipe_output_finish, + nullptr, + nullptr, + pipe_output_open, + pipe_output_close, + nullptr, + nullptr, + pipe_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/pipe_output_plugin.h b/src/output/PipeOutputPlugin.hxx index 9f014f829..f0c29706b 100644 --- a/src/output/pipe_output_plugin.h +++ b/src/output/PipeOutputPlugin.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_PIPE_OUTPUT_PLUGIN_H -#define MPD_PIPE_OUTPUT_PLUGIN_H +#ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX +#define MPD_PIPE_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin pipe_output_plugin; diff --git a/src/output/pulse_output_plugin.c b/src/output/PulseOutputPlugin.cxx index e267427df..ab797387d 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/PulseOutputPlugin.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,10 +18,13 @@ */ #include "config.h" -#include "pulse_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "mixer/pulse_mixer_plugin.h" +#include "PulseOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "mixer/PulseMixerPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" #include <glib.h> @@ -38,77 +41,62 @@ #define MPD_PULSE_NAME "Music Player Daemon" -#if !defined(PA_CHECK_VERSION) -/** - * This macro was implemented in libpulse 0.9.16. - */ -#define PA_CHECK_VERSION(a,b,c) false -#endif - -struct pulse_output { +struct PulseOutput { struct audio_output base; const char *name; const char *server; const char *sink; - struct pulse_mixer *mixer; + PulseMixer *mixer; struct pa_threaded_mainloop *mainloop; struct pa_context *context; struct pa_stream *stream; size_t writable; - -#if !PA_CHECK_VERSION(0,9,11) - /** - * We need this variable because pa_stream_is_corked() wasn't - * added before 0.9.11. - */ - bool pause; -#endif }; -/** - * The quark used for GError.domain. - */ -static inline GQuark -pulse_output_quark(void) +static constexpr Domain pulse_output_domain("pulse_output"); + +static void +SetError(Error &error, pa_context *context, const char *msg) { - return g_quark_from_static_string("pulse_output"); + const int e = pa_context_errno(context); + error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e)); } void -pulse_output_lock(struct pulse_output *po) +pulse_output_lock(PulseOutput *po) { pa_threaded_mainloop_lock(po->mainloop); } void -pulse_output_unlock(struct pulse_output *po) +pulse_output_unlock(PulseOutput *po) { pa_threaded_mainloop_unlock(po->mainloop); } void -pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) +pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm) { - assert(po != NULL); - assert(po->mixer == NULL); - assert(pm != NULL); + assert(po != nullptr); + assert(po->mixer == nullptr); + assert(pm != nullptr); po->mixer = pm; - if (po->mainloop == NULL) + if (po->mainloop == nullptr) return; pa_threaded_mainloop_lock(po->mainloop); - if (po->context != NULL && + if (po->context != nullptr && pa_context_get_state(po->context) == PA_CONTEXT_READY) { pulse_mixer_on_connect(pm, po->context); - if (po->stream != NULL && + if (po->stream != nullptr && pa_stream_get_state(po->stream) == PA_STREAM_READY) pulse_mixer_on_change(pm, po->context, po->stream); } @@ -117,35 +105,33 @@ pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) } void -pulse_output_clear_mixer(struct pulse_output *po, - G_GNUC_UNUSED struct pulse_mixer *pm) +pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm) { - assert(po != NULL); - assert(pm != NULL); + assert(po != nullptr); + assert(pm != nullptr); assert(po->mixer == pm); - po->mixer = NULL; + po->mixer = nullptr; } bool -pulse_output_set_volume(struct pulse_output *po, - const struct pa_cvolume *volume, GError **error_r) +pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume, + Error &error) { pa_operation *o; - if (po->context == NULL || po->stream == NULL || + if (po->context == nullptr || po->stream == nullptr || pa_stream_get_state(po->stream) != PA_STREAM_READY) { - g_set_error(error_r, pulse_output_quark(), 0, "disconnected"); + error.Set(pulse_output_domain, "disconnected"); return false; } o = pa_context_set_sink_input_volume(po->context, pa_stream_get_index(po->stream), - volume, NULL, NULL); - if (o == NULL) { - g_set_error(error_r, pulse_output_quark(), 0, - "failed to set PulseAudio volume: %s", - pa_strerror(pa_context_errno(po->context))); + volume, nullptr, nullptr); + if (o == nullptr) { + SetError(error, po->context, + "failed to set PulseAudio volume"); return false; } @@ -166,8 +152,8 @@ pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, { pa_operation_state_t state; - assert(mainloop != NULL); - assert(operation != NULL); + assert(mainloop != nullptr); + assert(operation != nullptr); state = pa_operation_get_state(operation); while (state == PA_OPERATION_RUNNING) { @@ -185,10 +171,10 @@ pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, * the caller thread, to wake pulse_wait_for_operation() up. */ static void -pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s, - G_GNUC_UNUSED int success, void *userdata) +pulse_output_stream_success_cb(gcc_unused pa_stream *s, + gcc_unused int success, void *userdata) { - struct pulse_output *po = userdata; + PulseOutput *po = (PulseOutput *)userdata; pa_threaded_mainloop_signal(po->mainloop, 0); } @@ -196,11 +182,11 @@ pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s, static void pulse_output_context_state_cb(struct pa_context *context, void *userdata) { - struct pulse_output *po = userdata; + PulseOutput *po = (PulseOutput *)userdata; switch (pa_context_get_state(context)) { case PA_CONTEXT_READY: - if (po->mixer != NULL) + if (po->mixer != nullptr) pulse_mixer_on_connect(po->mixer, context); pa_threaded_mainloop_signal(po->mainloop, 0); @@ -208,7 +194,7 @@ pulse_output_context_state_cb(struct pa_context *context, void *userdata) case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: - if (po->mixer != NULL) + if (po->mixer != nullptr) pulse_mixer_on_disconnect(po->mixer); /* the caller thread might be waiting for these @@ -229,15 +215,15 @@ pulse_output_subscribe_cb(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { - struct pulse_output *po = userdata; - pa_subscription_event_type_t facility - = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; - pa_subscription_event_type_t type - = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + PulseOutput *po = (PulseOutput *)userdata; + pa_subscription_event_type_t facility = + pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + pa_subscription_event_type_t type = + pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); - if (po->mixer != NULL && + if (po->mixer != nullptr && facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && - po->stream != NULL && + po->stream != nullptr && pa_stream_get_state(po->stream) == PA_STREAM_READY && idx == pa_stream_get_index(po->stream) && (type == PA_SUBSCRIPTION_EVENT_NEW || @@ -251,19 +237,15 @@ pulse_output_subscribe_cb(pa_context *context, * @return true on success, false on error */ static bool -pulse_output_connect(struct pulse_output *po, GError **error_r) +pulse_output_connect(PulseOutput *po, Error &error) { - assert(po != NULL); - assert(po->context != NULL); + assert(po != nullptr); + assert(po->context != nullptr); - int error; - - error = pa_context_connect(po->context, po->server, - (pa_context_flags_t)0, NULL); - if (error < 0) { - g_set_error(error_r, pulse_output_quark(), 0, - "pa_context_connect() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + if (pa_context_connect(po->context, po->server, + (pa_context_flags_t)0, nullptr) < 0) { + SetError(error, po->context, + "pa_context_connect() has failed"); return false; } @@ -274,21 +256,19 @@ pulse_output_connect(struct pulse_output *po, GError **error_r) * Frees and clears the stream. */ static void -pulse_output_delete_stream(struct pulse_output *po) +pulse_output_delete_stream(PulseOutput *po) { - assert(po != NULL); - assert(po->stream != NULL); + assert(po != nullptr); + assert(po->stream != nullptr); -#if PA_CHECK_VERSION(0,9,8) - pa_stream_set_suspended_callback(po->stream, NULL, NULL); -#endif + pa_stream_set_suspended_callback(po->stream, nullptr, nullptr); - pa_stream_set_state_callback(po->stream, NULL, NULL); - pa_stream_set_write_callback(po->stream, NULL, NULL); + pa_stream_set_state_callback(po->stream, nullptr, nullptr); + pa_stream_set_write_callback(po->stream, nullptr, nullptr); pa_stream_disconnect(po->stream); pa_stream_unref(po->stream); - po->stream = NULL; + po->stream = nullptr; } /** @@ -297,17 +277,17 @@ pulse_output_delete_stream(struct pulse_output *po) * Caller must lock the main loop. */ static void -pulse_output_delete_context(struct pulse_output *po) +pulse_output_delete_context(PulseOutput *po) { - assert(po != NULL); - assert(po->context != NULL); + assert(po != nullptr); + assert(po->context != nullptr); - pa_context_set_state_callback(po->context, NULL, NULL); - pa_context_set_subscribe_callback(po->context, NULL, NULL); + pa_context_set_state_callback(po->context, nullptr, nullptr); + pa_context_set_subscribe_callback(po->context, nullptr, nullptr); pa_context_disconnect(po->context); pa_context_unref(po->context); - po->context = NULL; + po->context = nullptr; } /** @@ -318,16 +298,15 @@ pulse_output_delete_context(struct pulse_output *po) * @return true on success, false on error */ static bool -pulse_output_setup_context(struct pulse_output *po, GError **error_r) +pulse_output_setup_context(PulseOutput *po, Error &error) { - assert(po != NULL); - assert(po->mainloop != NULL); + assert(po != nullptr); + assert(po->mainloop != nullptr); po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), MPD_PULSE_NAME); - if (po->context == NULL) { - g_set_error(error_r, pulse_output_quark(), 0, - "pa_context_new() has failed"); + if (po->context == nullptr) { + error.Set(pulse_output_domain, "pa_context_new() has failed"); return false; } @@ -336,7 +315,7 @@ pulse_output_setup_context(struct pulse_output *po, GError **error_r) pa_context_set_subscribe_callback(po->context, pulse_output_subscribe_cb, po); - if (!pulse_output_connect(po, error_r)) { + if (!pulse_output_connect(po, error)) { pulse_output_delete_context(po); return false; } @@ -345,26 +324,26 @@ pulse_output_setup_context(struct pulse_output *po, GError **error_r) } static struct audio_output * -pulse_output_init(const struct config_param *param, GError **error_r) +pulse_output_init(const config_param ¶m, Error &error) { - struct pulse_output *po; + PulseOutput *po; g_setenv("PULSE_PROP_media.role", "music", true); - po = g_new(struct pulse_output, 1); - if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) { - g_free(po); - return NULL; + po = new PulseOutput(); + if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) { + delete po; + return nullptr; } - po->name = config_get_block_string(param, "name", "mpd_pulse"); - po->server = config_get_block_string(param, "server", NULL); - po->sink = config_get_block_string(param, "sink", NULL); + po->name = param.GetBlockValue("name", "mpd_pulse"); + po->server = param.GetBlockValue("server"); + po->sink = param.GetBlockValue("sink"); - po->mixer = NULL; - po->mainloop = NULL; - po->context = NULL; - po->stream = NULL; + po->mixer = nullptr; + po->mainloop = nullptr; + po->context = nullptr; + po->stream = nullptr; return &po->base; } @@ -372,28 +351,28 @@ pulse_output_init(const struct config_param *param, GError **error_r) static void pulse_output_finish(struct audio_output *ao) { - struct pulse_output *po = (struct pulse_output *)ao; + PulseOutput *po = (PulseOutput *)ao; ao_base_finish(&po->base); - g_free(po); + delete po; } static bool -pulse_output_enable(struct audio_output *ao, GError **error_r) +pulse_output_enable(struct audio_output *ao, Error &error) { - struct pulse_output *po = (struct pulse_output *)ao; + PulseOutput *po = (PulseOutput *)ao; - assert(po->mainloop == NULL); - assert(po->context == NULL); + assert(po->mainloop == nullptr); + assert(po->context == nullptr); /* create the libpulse mainloop and start the thread */ po->mainloop = pa_threaded_mainloop_new(); - if (po->mainloop == NULL) { + if (po->mainloop == nullptr) { g_free(po); - g_set_error(error_r, pulse_output_quark(), 0, - "pa_threaded_mainloop_new() has failed"); + error.Set(pulse_output_domain, + "pa_threaded_mainloop_new() has failed"); return false; } @@ -402,20 +381,20 @@ pulse_output_enable(struct audio_output *ao, GError **error_r) if (pa_threaded_mainloop_start(po->mainloop) < 0) { pa_threaded_mainloop_unlock(po->mainloop); pa_threaded_mainloop_free(po->mainloop); - po->mainloop = NULL; + po->mainloop = nullptr; - g_set_error(error_r, pulse_output_quark(), 0, - "pa_threaded_mainloop_start() has failed"); + error.Set(pulse_output_domain, + "pa_threaded_mainloop_start() has failed"); return false; } /* create the libpulse context and connect it */ - if (!pulse_output_setup_context(po, error_r)) { + if (!pulse_output_setup_context(po, error)) { pa_threaded_mainloop_unlock(po->mainloop); pa_threaded_mainloop_stop(po->mainloop); pa_threaded_mainloop_free(po->mainloop); - po->mainloop = NULL; + po->mainloop = nullptr; return false; } @@ -427,15 +406,15 @@ pulse_output_enable(struct audio_output *ao, GError **error_r) static void pulse_output_disable(struct audio_output *ao) { - struct pulse_output *po = (struct pulse_output *)ao; + PulseOutput *po = (PulseOutput *)ao; - assert(po->mainloop != NULL); + assert(po->mainloop != nullptr); pa_threaded_mainloop_stop(po->mainloop); - if (po->context != NULL) + if (po->context != nullptr) pulse_output_delete_context(po); pa_threaded_mainloop_free(po->mainloop); - po->mainloop = NULL; + po->mainloop = nullptr; } /** @@ -447,13 +426,13 @@ pulse_output_disable(struct audio_output *ao) * @return true on success, false on error */ static bool -pulse_output_wait_connection(struct pulse_output *po, GError **error_r) +pulse_output_wait_connection(PulseOutput *po, Error &error) { - assert(po->mainloop != NULL); + assert(po->mainloop != nullptr); pa_context_state_t state; - if (po->context == NULL && !pulse_output_setup_context(po, error_r)) + if (po->context == nullptr && !pulse_output_setup_context(po, error)) return false; while (true) { @@ -467,9 +446,7 @@ pulse_output_wait_connection(struct pulse_output *po, GError **error_r) case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: /* failure */ - g_set_error(error_r, pulse_output_quark(), 0, - "failed to connect: %s", - pa_strerror(pa_context_errno(po->context))); + SetError(error, po->context, "failed to connect"); pulse_output_delete_context(po); return false; @@ -483,35 +460,31 @@ pulse_output_wait_connection(struct pulse_output *po, GError **error_r) } } -#if PA_CHECK_VERSION(0,9,8) - static void -pulse_output_stream_suspended_cb(G_GNUC_UNUSED pa_stream *stream, void *userdata) +pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata) { - struct pulse_output *po = userdata; + PulseOutput *po = (PulseOutput *)userdata; - assert(stream == po->stream || po->stream == NULL); - assert(po->mainloop != NULL); + assert(stream == po->stream || po->stream == nullptr); + assert(po->mainloop != nullptr); /* wake up the main loop to break out of the loop in pulse_output_play() */ pa_threaded_mainloop_signal(po->mainloop, 0); } -#endif - static void pulse_output_stream_state_cb(pa_stream *stream, void *userdata) { - struct pulse_output *po = userdata; + PulseOutput *po = (PulseOutput *)userdata; - assert(stream == po->stream || po->stream == NULL); - assert(po->mainloop != NULL); - assert(po->context != NULL); + assert(stream == po->stream || po->stream == nullptr); + assert(po->mainloop != nullptr); + assert(po->context != nullptr); switch (pa_stream_get_state(stream)) { case PA_STREAM_READY: - if (po->mixer != NULL) + if (po->mixer != nullptr) pulse_mixer_on_change(po->mixer, po->context, stream); pa_threaded_mainloop_signal(po->mainloop, 0); @@ -519,7 +492,7 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata) case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: - if (po->mixer != NULL) + if (po->mixer != nullptr) pulse_mixer_on_disconnect(po->mixer); pa_threaded_mainloop_signal(po->mainloop, 0); @@ -532,12 +505,12 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata) } static void -pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes, +pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes, void *userdata) { - struct pulse_output *po = userdata; + PulseOutput *po = (PulseOutput *)userdata; - assert(po->mainloop != NULL); + assert(po->mainloop != nullptr); po->writable = nbytes; pa_threaded_mainloop_signal(po->mainloop, 0); @@ -551,24 +524,20 @@ pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes, * @return true on success, false on error */ static bool -pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss, - GError **error_r) +pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, + Error &error) { - assert(po != NULL); - assert(po->context != NULL); + assert(po != nullptr); + assert(po->context != nullptr); - po->stream = pa_stream_new(po->context, po->name, ss, NULL); - if (po->stream == NULL) { - g_set_error(error_r, pulse_output_quark(), 0, - "pa_stream_new() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + po->stream = pa_stream_new(po->context, po->name, ss, nullptr); + if (po->stream == nullptr) { + SetError(error, po->context, "pa_stream_new() has failed"); return false; } -#if PA_CHECK_VERSION(0,9,8) pa_stream_set_suspended_callback(po->stream, pulse_output_stream_suspended_cb, po); -#endif pa_stream_set_state_callback(po->stream, pulse_output_stream_state_cb, po); @@ -579,18 +548,17 @@ pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss, } static bool -pulse_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) +pulse_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { - struct pulse_output *po = (struct pulse_output *)ao; + PulseOutput *po = (PulseOutput *)ao; pa_sample_spec ss; - int error; - assert(po->mainloop != NULL); + assert(po->mainloop != nullptr); pa_threaded_mainloop_lock(po->mainloop); - if (po->context != NULL) { + if (po->context != nullptr) { switch (pa_context_get_state(po->context)) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_TERMINATED: @@ -609,72 +577,68 @@ pulse_output_open(struct audio_output *ao, struct audio_format *audio_format, } } - if (!pulse_output_wait_connection(po, error_r)) { + if (!pulse_output_wait_connection(po, error)) { pa_threaded_mainloop_unlock(po->mainloop); return false; } /* MPD doesn't support the other pulseaudio sample formats, so we just force MPD to send us everything as 16 bit */ - audio_format->format = SAMPLE_FORMAT_S16; + audio_format.format = SampleFormat::S16; ss.format = PA_SAMPLE_S16NE; - ss.rate = audio_format->sample_rate; - ss.channels = audio_format->channels; + ss.rate = audio_format.sample_rate; + ss.channels = audio_format.channels; /* create a stream .. */ - if (!pulse_output_setup_stream(po, &ss, error_r)) { + if (!pulse_output_setup_stream(po, &ss, error)) { pa_threaded_mainloop_unlock(po->mainloop); return false; } /* .. and connect it (asynchronously) */ - error = pa_stream_connect_playback(po->stream, po->sink, - NULL, 0, NULL, NULL); - if (error < 0) { + if (pa_stream_connect_playback(po->stream, po->sink, + nullptr, pa_stream_flags_t(0), + nullptr, nullptr) < 0) { pulse_output_delete_stream(po); - g_set_error(error_r, pulse_output_quark(), 0, - "pa_stream_connect_playback() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + SetError(error, po->context, + "pa_stream_connect_playback() has failed"); pa_threaded_mainloop_unlock(po->mainloop); return false; } pa_threaded_mainloop_unlock(po->mainloop); -#if !PA_CHECK_VERSION(0,9,11) - po->pause = false; -#endif - return true; } static void pulse_output_close(struct audio_output *ao) { - struct pulse_output *po = (struct pulse_output *)ao; + PulseOutput *po = (PulseOutput *)ao; pa_operation *o; - assert(po->mainloop != NULL); + assert(po->mainloop != nullptr); pa_threaded_mainloop_lock(po->mainloop); if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { o = pa_stream_drain(po->stream, pulse_output_stream_success_cb, po); - if (o == NULL) { - g_warning("pa_stream_drain() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + if (o == nullptr) { + FormatWarning(pulse_output_domain, + "pa_stream_drain() has failed: %s", + pa_strerror(pa_context_errno(po->context))); } else pulse_wait_for_operation(po->mainloop, o); } pulse_output_delete_stream(po); - if (po->context != NULL && + if (po->context != nullptr && pa_context_get_state(po->context) != PA_CONTEXT_READY) pulse_output_delete_context(po); @@ -688,7 +652,7 @@ pulse_output_close(struct audio_output *ao) * @return true on success, false on error */ static bool -pulse_output_wait_stream(struct pulse_output *po, GError **error_r) +pulse_output_wait_stream(PulseOutput *po, Error &error) { while (true) { switch (pa_stream_get_state(po->stream)) { @@ -698,10 +662,8 @@ pulse_output_wait_stream(struct pulse_output *po, GError **error_r) case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: case PA_STREAM_UNCONNECTED: - g_set_error(error_r, pulse_output_quark(), - pa_context_errno(po->context), - "failed to connect the stream: %s", - pa_strerror(pa_context_errno(po->context))); + SetError(error, po->context, + "failed to connect the stream"); return false; case PA_STREAM_CREATING: @@ -712,65 +674,42 @@ pulse_output_wait_stream(struct pulse_output *po, GError **error_r) } /** - * Determines whether the stream is paused. On libpulse older than - * 0.9.11, it uses a custom pause flag. - */ -static bool -pulse_output_stream_is_paused(struct pulse_output *po) -{ - assert(po->stream != NULL); - -#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11) - return po->pause; -#else - return pa_stream_is_corked(po->stream); -#endif -} - -/** * Sets cork mode on the stream. */ static bool -pulse_output_stream_pause(struct pulse_output *po, bool pause, - GError **error_r) +pulse_output_stream_pause(PulseOutput *po, bool pause, + Error &error) { pa_operation *o; - assert(po->mainloop != NULL); - assert(po->context != NULL); - assert(po->stream != NULL); + assert(po->mainloop != nullptr); + assert(po->context != nullptr); + assert(po->stream != nullptr); o = pa_stream_cork(po->stream, pause, pulse_output_stream_success_cb, po); - if (o == NULL) { - g_set_error(error_r, pulse_output_quark(), 0, - "pa_stream_cork() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + if (o == nullptr) { + SetError(error, po->context, "pa_stream_cork() has failed"); return false; } if (!pulse_wait_for_operation(po->mainloop, o)) { - g_set_error(error_r, pulse_output_quark(), 0, - "pa_stream_cork() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + SetError(error, po->context, "pa_stream_cork() has failed"); return false; } -#if !PA_CHECK_VERSION(0,9,11) - po->pause = pause; -#endif return true; } static unsigned pulse_output_delay(struct audio_output *ao) { - struct pulse_output *po = (struct pulse_output *)ao; + PulseOutput *po = (PulseOutput *)ao; unsigned result = 0; pa_threaded_mainloop_lock(po->mainloop); - if (po->base.pause && pulse_output_stream_is_paused(po) && + if (po->base.pause && pa_stream_is_corked(po->stream) && pa_stream_get_state(po->stream) == PA_STREAM_READY) /* idle while paused */ result = 1000; @@ -782,29 +721,28 @@ pulse_output_delay(struct audio_output *ao) static size_t pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) + Error &error) { - struct pulse_output *po = (struct pulse_output *)ao; - int error; + PulseOutput *po = (PulseOutput *)ao; - assert(po->mainloop != NULL); - assert(po->stream != NULL); + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); pa_threaded_mainloop_lock(po->mainloop); /* check if the stream is (already) connected */ - if (!pulse_output_wait_stream(po, error_r)) { + if (!pulse_output_wait_stream(po, error)) { pa_threaded_mainloop_unlock(po->mainloop); return 0; } - assert(po->context != NULL); + assert(po->context != nullptr); /* unpause if previously paused */ - if (pulse_output_stream_is_paused(po) && - !pulse_output_stream_pause(po, false, error_r)) { + if (pa_stream_is_corked(po->stream) && + !pulse_output_stream_pause(po, false, error)) { pa_threaded_mainloop_unlock(po->mainloop); return 0; } @@ -812,21 +750,17 @@ pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, /* wait until the server allows us to write */ while (po->writable == 0) { -#if PA_CHECK_VERSION(0,9,8) if (pa_stream_is_suspended(po->stream)) { pa_threaded_mainloop_unlock(po->mainloop); - g_set_error(error_r, pulse_output_quark(), 0, - "suspended"); + error.Set(pulse_output_domain, "suspended"); return 0; } -#endif pa_threaded_mainloop_wait(po->mainloop); if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { pa_threaded_mainloop_unlock(po->mainloop); - g_set_error(error_r, pulse_output_quark(), 0, - "disconnected"); + error.Set(pulse_output_domain, "disconnected"); return 0; } } @@ -839,12 +773,11 @@ pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, po->writable -= size; - error = pa_stream_write(po->stream, chunk, size, NULL, - 0, PA_SEEK_RELATIVE); + int result = pa_stream_write(po->stream, chunk, size, nullptr, + 0, PA_SEEK_RELATIVE); pa_threaded_mainloop_unlock(po->mainloop); - if (error < 0) { - g_set_error(error_r, pulse_output_quark(), error, - "%s", pa_strerror(error)); + if (result < 0) { + SetError(error, po->context, "pa_stream_write() failed"); return 0; } @@ -854,11 +787,11 @@ pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, static void pulse_output_cancel(struct audio_output *ao) { - struct pulse_output *po = (struct pulse_output *)ao; + PulseOutput *po = (PulseOutput *)ao; pa_operation *o; - assert(po->mainloop != NULL); - assert(po->stream != NULL); + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); pa_threaded_mainloop_lock(po->mainloop); @@ -869,12 +802,13 @@ pulse_output_cancel(struct audio_output *ao) return; } - assert(po->context != NULL); + assert(po->context != nullptr); o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); - if (o == NULL) { - g_warning("pa_stream_flush() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + if (o == nullptr) { + FormatWarning(pulse_output_domain, + "pa_stream_flush() has failed: %s", + pa_strerror(pa_context_errno(po->context))); pa_threaded_mainloop_unlock(po->mainloop); return; } @@ -886,32 +820,30 @@ pulse_output_cancel(struct audio_output *ao) static bool pulse_output_pause(struct audio_output *ao) { - struct pulse_output *po = (struct pulse_output *)ao; - GError *error = NULL; + PulseOutput *po = (PulseOutput *)ao; - assert(po->mainloop != NULL); - assert(po->stream != NULL); + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); pa_threaded_mainloop_lock(po->mainloop); /* check if the stream is (already/still) connected */ - if (!pulse_output_wait_stream(po, &error)) { + Error error; + if (!pulse_output_wait_stream(po, error)) { pa_threaded_mainloop_unlock(po->mainloop); - g_warning("%s", error->message); - g_error_free(error); + LogError(error); return false; } - assert(po->context != NULL); + assert(po->context != nullptr); /* cork the stream */ - if (!pulse_output_stream_is_paused(po) && - !pulse_output_stream_pause(po, true, &error)) { + if (!pa_stream_is_corked(po->stream) && + !pulse_output_stream_pause(po, true, error)) { pa_threaded_mainloop_unlock(po->mainloop); - g_warning("%s", error->message); - g_error_free(error); + LogError(error); return false; } @@ -923,33 +855,35 @@ pulse_output_pause(struct audio_output *ao) static bool pulse_output_test_default_device(void) { - struct pulse_output *po; bool success; - po = (struct pulse_output *)pulse_output_init(NULL, NULL); - if (po == NULL) + const config_param empty; + PulseOutput *po = (PulseOutput *) + pulse_output_init(empty, IgnoreError()); + if (po == nullptr) return false; - success = pulse_output_wait_connection(po, NULL); + success = pulse_output_wait_connection(po, IgnoreError()); pulse_output_finish(&po->base); return success; } const struct audio_output_plugin pulse_output_plugin = { - .name = "pulse", - - .test_default_device = pulse_output_test_default_device, - .init = pulse_output_init, - .finish = pulse_output_finish, - .enable = pulse_output_enable, - .disable = pulse_output_disable, - .open = pulse_output_open, - .delay = pulse_output_delay, - .play = pulse_output_play, - .cancel = pulse_output_cancel, - .pause = pulse_output_pause, - .close = pulse_output_close, - - .mixer_plugin = &pulse_mixer_plugin, + "pulse", + pulse_output_test_default_device, + pulse_output_init, + pulse_output_finish, + pulse_output_enable, + pulse_output_disable, + pulse_output_open, + pulse_output_close, + pulse_output_delay, + nullptr, + pulse_output_play, + nullptr, + pulse_output_cancel, + pulse_output_pause, + + &pulse_mixer_plugin, }; diff --git a/src/output/pulse_output_plugin.h b/src/output/PulseOutputPlugin.hxx index 02a51f27b..0ed8404bc 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/PulseOutputPlugin.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,33 +17,30 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_PULSE_OUTPUT_PLUGIN_H -#define MPD_PULSE_OUTPUT_PLUGIN_H +#ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX +#define MPD_PULSE_OUTPUT_PLUGIN_HXX -#include <stdbool.h> - -#include <glib.h> - -struct pulse_output; -struct pulse_mixer; +struct PulseOutput; +struct PulseMixer; struct pa_cvolume; +class Error; extern const struct audio_output_plugin pulse_output_plugin; void -pulse_output_lock(struct pulse_output *po); +pulse_output_lock(PulseOutput *po); void -pulse_output_unlock(struct pulse_output *po); +pulse_output_unlock(PulseOutput *po); void -pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm); +pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm); void -pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm); +pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm); bool -pulse_output_set_volume(struct pulse_output *po, - const struct pa_cvolume *volume, GError **error_r); +pulse_output_set_volume(PulseOutput *po, + const struct pa_cvolume *volume, Error &error); #endif diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx new file mode 100644 index 000000000..9a7eba01f --- /dev/null +++ b/src/output/RecorderOutputPlugin.cxx @@ -0,0 +1,262 @@ +/* + * 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 "RecorderOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "EncoderPlugin.hxx" +#include "EncoderList.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/fd_util.h" +#include "open.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +struct RecorderOutput { + struct audio_output base; + + /** + * The configured encoder plugin. + */ + Encoder *encoder; + + /** + * The destination file name. + */ + const char *path; + + /** + * The destination file descriptor. + */ + int fd; + + /** + * The buffer for encoder_read(). + */ + char buffer[32768]; + + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &recorder_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); + + bool WriteToFile(const void *data, size_t length, Error &error); + + /** + * Writes pending data from the encoder to the output file. + */ + bool EncoderToFile(Error &error); +}; + +static constexpr Domain recorder_output_domain("recorder_output"); + +inline bool +RecorderOutput::Configure(const config_param ¶m, Error &error) +{ + /* read configuration */ + + const char *encoder_name = + param.GetBlockValue("encoder", "vorbis"); + const auto encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == nullptr) { + error.Format(config_domain, + "No such encoder: %s", encoder_name); + return false; + } + + path = param.GetBlockValue("path"); + if (path == nullptr) { + error.Set(config_domain, "'path' not configured"); + return false; + } + + /* initialize encoder */ + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + return true; +} + +static audio_output * +recorder_output_init(const config_param ¶m, Error &error) +{ + RecorderOutput *recorder = new RecorderOutput(); + + if (!recorder->Initialize(param, error)) { + delete recorder; + return nullptr; + } + + if (!recorder->Configure(param, error)) { + recorder->Deinitialize(); + delete recorder; + return nullptr; + } + + return &recorder->base; +} + +static void +recorder_output_finish(struct audio_output *ao) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + encoder_finish(recorder->encoder); + recorder->Deinitialize(); + delete recorder; +} + +inline bool +RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error) +{ + assert(length > 0); + + const uint8_t *data = (const uint8_t *)_data, *end = data + length; + + while (true) { + ssize_t nbytes = write(fd, data, end - data); + if (nbytes > 0) { + data += nbytes; + if (data == end) + return true; + } else if (nbytes == 0) { + /* shouldn't happen for files */ + error.Set(recorder_output_domain, + "write() returned 0"); + return false; + } else if (errno != EINTR) { + error.FormatErrno("Failed to write to '%s'", path); + return false; + } + } +} + +inline bool +RecorderOutput::EncoderToFile(Error &error) +{ + assert(fd >= 0); + + while (true) { + /* read from the encoder */ + + size_t size = encoder_read(encoder, buffer, sizeof(buffer)); + if (size == 0) + return true; + + /* write everything into the file */ + + if (!WriteToFile(buffer, size, error)) + return false; + } +} + +static bool +recorder_output_open(struct audio_output *ao, + AudioFormat &audio_format, + Error &error) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + /* create the output file */ + + recorder->fd = open_cloexec(recorder->path, + O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, + 0666); + if (recorder->fd < 0) { + error.FormatErrno("Failed to create '%s'", recorder->path); + return false; + } + + /* open the encoder */ + + if (!encoder_open(recorder->encoder, audio_format, error)) { + close(recorder->fd); + unlink(recorder->path); + return false; + } + + if (!recorder->EncoderToFile(error)) { + encoder_close(recorder->encoder); + close(recorder->fd); + unlink(recorder->path); + return false; + } + + return true; +} + +static void +recorder_output_close(struct audio_output *ao) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + /* flush the encoder and write the rest to the file */ + + if (encoder_end(recorder->encoder, IgnoreError())) + recorder->EncoderToFile(IgnoreError()); + + /* now really close everything */ + + encoder_close(recorder->encoder); + + close(recorder->fd); +} + +static size_t +recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + return encoder_write(recorder->encoder, chunk, size, error) && + recorder->EncoderToFile(error) + ? size : 0; +} + +const struct audio_output_plugin recorder_output_plugin = { + "recorder", + nullptr, + recorder_output_init, + recorder_output_finish, + nullptr, + nullptr, + recorder_output_open, + recorder_output_close, + nullptr, + nullptr, + recorder_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/recorder_output_plugin.h b/src/output/RecorderOutputPlugin.hxx index a9bf755bd..a27f51e23 100644 --- a/src/output/recorder_output_plugin.h +++ b/src/output/RecorderOutputPlugin.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_RECORDER_OUTPUT_PLUGIN_H -#define MPD_RECORDER_OUTPUT_PLUGIN_H +#ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX +#define MPD_RECORDER_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin recorder_output_plugin; diff --git a/src/output/roar_output_plugin.c b/src/output/RoarOutputPlugin.cxx index 1c2c48321..9d66bb63b 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,23 @@ */ #include "config.h" -#include "roar_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "roar_output_plugin.h" +#include "RoarOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "MixerList.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" #include <glib.h> -#include <stdint.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> +/* libroar/services.h declares roar_service_stream::new - work around + this C++ problem */ +#define new _new #include <roaraudio.h> +#undef new -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "roaraudio" - -typedef struct roar -{ +struct RoarOutput { struct audio_output base; roar_vs_t * vss; @@ -47,20 +45,25 @@ typedef struct roar int role; struct roar_connection con; struct roar_audio_info info; - GMutex *lock; + Mutex mutex; volatile bool alive; -} roar_t; -static inline GQuark -roar_output_quark(void) -{ - return g_quark_from_static_string("roar_output"); -} + RoarOutput() + :err(ROAR_ERROR_NONE), + host(nullptr), name(nullptr) {} + + ~RoarOutput() { + g_free(host); + g_free(name); + } +}; + +static constexpr Domain roar_output_domain("roar_output"); 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,38 +97,34 @@ 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 config_param ¶m) { - self->host = config_dup_block_string(param, "server", NULL); - self->name = config_dup_block_string(param, "name", "MPD"); + self->host = param.DupBlockString("server", nullptr); + self->name = param.DupBlockString("name", "MPD"); - const char *role = config_get_block_string(param, "role", "music"); - self->role = role != NULL + const char *role = param.GetBlockValue("role", "music"); + self->role = role != nullptr ? roar_str2role(role) : ROAR_ROLE_MUSIC; } static struct audio_output * -roar_init(const struct config_param *param, GError **error_r) +roar_init(const config_param ¶m, Error &error) { - 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; + if (!ao_base_init(&self->base, &roar_output_plugin, param, error)) { + delete self; + return nullptr; } - self->lock = g_mutex_new(); - self->err = ROAR_ERROR_NONE; roar_configure(self, param); return &self->base; } @@ -135,70 +132,65 @@ 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 roar_use_audio_format(struct roar_audio_info *info, - struct audio_format *audio_format) + AudioFormat &audio_format) { - info->rate = audio_format->sample_rate; - info->channels = audio_format->channels; + info->rate = audio_format.sample_rate; + info->channels = audio_format.channels; info->codec = ROAR_CODEC_PCM_S; - switch (audio_format->format) { - case SAMPLE_FORMAT_UNDEFINED: + switch (audio_format.format) { + case SampleFormat::UNDEFINED: + case SampleFormat::FLOAT: + case SampleFormat::DSD: info->bits = 16; - audio_format->format = SAMPLE_FORMAT_S16; + audio_format.format = SampleFormat::S16; break; - case SAMPLE_FORMAT_S8: + case SampleFormat::S8: info->bits = 8; break; - case SAMPLE_FORMAT_S16: + case SampleFormat::S16: info->bits = 16; break; - case SAMPLE_FORMAT_S24_P32: + case SampleFormat::S24_P32: info->bits = 32; - audio_format->format = SAMPLE_FORMAT_S32; + audio_format.format = SampleFormat::S32; break; - case SAMPLE_FORMAT_S32: + case SampleFormat::S32: info->bits = 32; break; } } static bool -roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &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); + error.Set(roar_output_domain, + "Failed to connect to Roar server"); 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); + error.Set(roar_output_domain, "Failed to connect to server"); return false; } @@ -207,50 +199,48 @@ roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **e if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, &(self->err)) < 0) { - g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); - g_mutex_unlock(self->lock); + error.Set(roar_output_domain, "Failed to start stream"); 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, &(self->err)) < 0) { roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); - g_warning("Failed to start stream"); + LogError(roar_output_domain, "Failed to start stream"); return; } @@ -262,29 +252,29 @@ 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) +roar_play(struct audio_output *ao, const void *chunk, size_t size, + Error &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"); + error.Set(roar_output_domain, "Connection is invalid"); return 0; } rc = roar_vs_write(self->vss, chunk, size, &(self->err)); if ( rc <= 0 ) { - g_set_error(error, roar_output_quark(), 0, "Failed to play data"); + error.Set(roar_output_domain, "Failed to play data"); return 0; } @@ -332,19 +322,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) +roar_send_tag(struct audio_output *ao, const 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 +352,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 +374,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/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx new file mode 100644 index 000000000..19f2b61cd --- /dev/null +++ b/src/output/ShoutOutputPlugin.cxx @@ -0,0 +1,544 @@ +/* + * 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 "ShoutOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "EncoderPlugin.hxx" +#include "EncoderList.hxx" +#include "ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <shout/shout.h> +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2; + +struct ShoutOutput final { + struct audio_output base; + + shout_t *shout_conn; + shout_metadata_t *shout_meta; + + Encoder *encoder; + + float quality; + int bitrate; + + int timeout; + + uint8_t buffer[32768]; + + ShoutOutput() + :shout_conn(shout_new()), + shout_meta(shout_metadata_new()), + quality(-2.0), + bitrate(-1), + timeout(DEFAULT_CONN_TIMEOUT) {} + + ~ShoutOutput() { + if (shout_meta != nullptr) + shout_metadata_free(shout_meta); + if (shout_conn != nullptr) + shout_free(shout_conn); + } + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &shout_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static int shout_init_count; + +static constexpr Domain shout_output_domain("shout_output"); + +static const EncoderPlugin * +shout_encoder_plugin_get(const char *name) +{ + if (strcmp(name, "ogg") == 0) + name = "vorbis"; + else if (strcmp(name, "mp3") == 0) + name = "lame"; + + return encoder_plugin_get(name); +} + +gcc_pure +static const char * +require_block_string(const config_param ¶m, const char *name) +{ + const char *value = param.GetBlockValue(name); + if (value == nullptr) + FormatFatalError("no \"%s\" defined for shout device defined " + "at line %u\n", name, param.line); + + return value; +} + +inline bool +ShoutOutput::Configure(const config_param ¶m, Error &error) +{ + + const AudioFormat audio_format = base.config_audio_format; + if (!audio_format.IsFullyDefined()) { + error.Set(config_domain, + "Need full audio format specification"); + return nullptr; + } + + const char *host = require_block_string(param, "host"); + const char *mount = require_block_string(param, "mount"); + unsigned port = param.GetBlockValue("port", 0u); + if (port == 0) { + error.Set(config_domain, "shout port must be configured"); + return false; + } + + const char *passwd = require_block_string(param, "password"); + const char *name = require_block_string(param, "name"); + + bool is_public = param.GetBlockValue("public", false); + + const char *user = param.GetBlockValue("user", "source"); + + const char *value = param.GetBlockValue("quality"); + if (value != nullptr) { + char *test; + quality = strtod(value, &test); + + if (*test != '\0' || quality < -1.0 || quality > 10.0) { + error.Format(config_domain, + "shout quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are " + "both defined"); + return false; + } + } else { + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + char *test; + bitrate = strtol(value, &test, 10); + + if (*test != '\0' || bitrate <= 0) { + error.Set(config_domain, + "bitrate must be a positive integer"); + return false; + } + } + + const char *encoding = param.GetBlockValue("encoding", "ogg"); + const auto encoder_plugin = shout_encoder_plugin_get(encoding); + if (encoder_plugin == nullptr) { + error.Format(config_domain, + "couldn't find shout encoder plugin \"%s\"", + encoding); + return false; + } + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + unsigned shout_format; + if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) + shout_format = SHOUT_FORMAT_MP3; + else + shout_format = SHOUT_FORMAT_OGG; + + unsigned protocol; + value = param.GetBlockValue("protocol"); + if (value != nullptr) { + if (0 == strcmp(value, "shoutcast") && + 0 != strcmp(encoding, "mp3")) { + error.Format(config_domain, + "you cannot stream \"%s\" to shoutcast, use mp3", + encoding); + return false; + } else if (0 == strcmp(value, "shoutcast")) + protocol = SHOUT_PROTOCOL_ICY; + else if (0 == strcmp(value, "icecast1")) + protocol = SHOUT_PROTOCOL_XAUDIOCAST; + else if (0 == strcmp(value, "icecast2")) + protocol = SHOUT_PROTOCOL_HTTP; + else { + error.Format(config_domain, + "shout protocol \"%s\" is not \"shoutcast\" or " + "\"icecast1\"or \"icecast2\"", + value); + return false; + } + } else { + protocol = SHOUT_PROTOCOL_HTTP; + } + + if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS || + shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS || + shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS || + shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS || + shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS || + shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS || + shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS || + shout_set_format(shout_conn, shout_format) + != SHOUTERR_SUCCESS || + shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS || + shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + /* optional paramters */ + timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT); + + value = param.GetBlockValue("genre"); + if (value != nullptr && shout_set_genre(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + value = param.GetBlockValue("description"); + if (value != nullptr && shout_set_description(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + value = param.GetBlockValue("url"); + if (value != nullptr && shout_set_url(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + { + char temp[11]; + memset(temp, 0, sizeof(temp)); + + snprintf(temp, sizeof(temp), "%u", audio_format.channels); + shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp); + + snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate); + + shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp); + + if (quality >= -1.0) { + snprintf(temp, sizeof(temp), "%2.2f", quality); + shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY, + temp); + } else { + snprintf(temp, sizeof(temp), "%d", bitrate); + shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE, + temp); + } + } + + return true; +} + +static struct audio_output * +my_shout_init_driver(const config_param ¶m, Error &error) +{ + ShoutOutput *sd = new ShoutOutput(); + if (!sd->Initialize(param, error)) { + delete sd; + return nullptr; + } + + if (!sd->Configure(param, error)) { + sd->Deinitialize(); + delete sd; + return nullptr; + } + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + return &sd->base; +} + +static bool +handle_shout_error(ShoutOutput *sd, int err, Error &error) +{ + switch (err) { + case SHOUTERR_SUCCESS: + break; + + case SHOUTERR_UNCONNECTED: + case SHOUTERR_SOCKET: + error.Format(shout_output_domain, err, + "Lost shout connection to %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + + default: + error.Format(shout_output_domain, err, + "connection to %s:%i error: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } + + return true; +} + +static bool +write_page(ShoutOutput *sd, Error &error) +{ + assert(sd->encoder != nullptr); + + while (true) { + size_t nbytes = encoder_read(sd->encoder, + sd->buffer, sizeof(sd->buffer)); + if (nbytes == 0) + return true; + + int err = shout_send(sd->shout_conn, sd->buffer, nbytes); + if (!handle_shout_error(sd, err, error)) + return false; + } + + return true; +} + +static void close_shout_conn(ShoutOutput * sd) +{ + if (sd->encoder != nullptr) { + if (encoder_end(sd->encoder, IgnoreError())) + write_page(sd, IgnoreError()); + + encoder_close(sd->encoder); + } + + if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && + shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { + FormatWarning(shout_output_domain, + "problem closing connection to shout server: %s", + shout_get_error(sd->shout_conn)); + } +} + +static void +my_shout_finish_driver(struct audio_output *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + encoder_finish(sd->encoder); + + sd->Deinitialize(); + delete sd; + + shout_init_count--; + + if (shout_init_count == 0) + shout_shutdown(); +} + +static void +my_shout_drop_buffered_audio(struct audio_output *ao) +{ + gcc_unused + ShoutOutput *sd = (ShoutOutput *)ao; + + /* needs to be implemented for shout */ +} + +static void +my_shout_close_device(struct audio_output *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + close_shout_conn(sd); +} + +static bool +shout_connect(ShoutOutput *sd, Error &error) +{ + switch (shout_open(sd->shout_conn)) { + case SHOUTERR_SUCCESS: + case SHOUTERR_CONNECTED: + return true; + + default: + error.Format(shout_output_domain, + "problem opening connection to shout server %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } +} + +static bool +my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format, + Error &error) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + if (!shout_connect(sd, error)) + return false; + + if (!encoder_open(sd->encoder, audio_format, error)) { + shout_close(sd->shout_conn); + return false; + } + + if (!write_page(sd, error)) { + encoder_close(sd->encoder); + shout_close(sd->shout_conn); + return false; + } + + return true; +} + +static unsigned +my_shout_delay(struct audio_output *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + int delay = shout_delay(sd->shout_conn); + if (delay < 0) + delay = 0; + + return delay; +} + +static size_t +my_shout_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + return encoder_write(sd->encoder, chunk, size, error) && + write_page(sd, error) + ? size + : 0; +} + +static bool +my_shout_pause(struct audio_output *ao) +{ + static char silence[1020]; + + return my_shout_play(ao, silence, sizeof(silence), IgnoreError()); +} + +static void +shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) +{ + char artist[size]; + char title[size]; + + artist[0] = 0; + title[0] = 0; + + for (unsigned i = 0; i < tag->num_items; i++) { + switch (tag->items[i]->type) { + case TAG_ARTIST: + strncpy(artist, tag->items[i]->value, size); + break; + case TAG_TITLE: + strncpy(title, tag->items[i]->value, size); + break; + + default: + break; + } + } + + snprintf(dest, size, "%s - %s", artist, title); +} + +static void my_shout_set_tag(struct audio_output *ao, + const Tag *tag) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + if (sd->encoder->plugin.tag != nullptr) { + /* encoder plugin supports stream tags */ + + Error error; + if (!encoder_pre_tag(sd->encoder, error) || + !write_page(sd, error) || + !encoder_tag(sd->encoder, tag, error)) { + LogError(error); + return; + } + } else { + /* no stream tag support: fall back to icy-metadata */ + char song[1024]; + shout_tag_to_metadata(tag, song, sizeof(song)); + + shout_metadata_add(sd->shout_meta, "song", song); + if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, + sd->shout_meta)) { + LogWarning(shout_output_domain, + "error setting shout metadata"); + } + } + + write_page(sd, IgnoreError()); +} + +const struct audio_output_plugin shout_output_plugin = { + "shout", + nullptr, + my_shout_init_driver, + my_shout_finish_driver, + nullptr, + nullptr, + my_shout_open_device, + my_shout_close_device, + my_shout_delay, + my_shout_set_tag, + my_shout_play, + nullptr, + my_shout_drop_buffered_audio, + my_shout_pause, + nullptr, +}; diff --git a/src/output/shout_output_plugin.h b/src/output/ShoutOutputPlugin.hxx index 9a7378803..496b77975 100644 --- a/src/output/shout_output_plugin.h +++ b/src/output/ShoutOutputPlugin.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_SHOUT_OUTPUT_PLUGIN_H -#define MPD_SHOUT_OUTPUT_PLUGIN_H +#ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX +#define MPD_SHOUT_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin shout_output_plugin; diff --git a/src/output/solaris_output_plugin.c b/src/output/SolarisOutputPlugin.cxx index ce726009a..0836dc2e2 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/SolarisOutputPlugin.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 "solaris_output_plugin.h" -#include "output_api.h" -#include "fd_util.h" - -#include <glib.h> +#include "SolarisOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" #include <sys/stropts.h> #include <sys/types.h> @@ -50,26 +49,23 @@ struct audio_info { #endif -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "solaris_output" - -struct solaris_output { +struct SolarisOutput { struct audio_output base; /* configuration */ const char *device; int fd; -}; -/** - * The quark used for GError.domain. - */ -static inline GQuark -solaris_output_quark(void) -{ - return g_quark_from_static_string("solaris_output"); -} + bool Initialize(const config_param ¶m, Error &error_r) { + return ao_base_init(&base, &solaris_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; static bool solaris_output_test_default_device(void) @@ -81,16 +77,15 @@ solaris_output_test_default_device(void) } static struct audio_output * -solaris_output_init(const struct config_param *param, GError **error_r) +solaris_output_init(const config_param ¶m, Error &error_r) { - struct solaris_output *so = g_new(struct solaris_output, 1); - - if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) { - g_free(so); - return NULL; + SolarisOutput *so = new SolarisOutput(); + if (!so->Initialize(param, error_r)) { + delete so; + return nullptr; } - so->device = config_get_block_string(param, "device", "/dev/audio"); + so->device = param.GetBlockValue("device", "/dev/audio"); return &so->base; } @@ -98,31 +93,30 @@ solaris_output_init(const struct config_param *param, GError **error_r) static void solaris_output_finish(struct audio_output *ao) { - struct solaris_output *so = (struct solaris_output *)ao; + SolarisOutput *so = (SolarisOutput *)ao; - ao_base_finish(&so->base); - g_free(so); + so->Deinitialize(); + delete so; } static bool -solaris_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) +solaris_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { - struct solaris_output *so = (struct solaris_output *)ao; + SolarisOutput *so = (SolarisOutput *)ao; struct audio_info info; int ret, flags; /* support only 16 bit mono/stereo for now; nothing else has been tested */ - audio_format->format = SAMPLE_FORMAT_S16; + audio_format.format = SampleFormat::S16; /* open the device in non-blocking mode */ so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0); if (so->fd < 0) { - g_set_error(error, solaris_output_quark(), errno, - "Failed to open %s: %s", - so->device, g_strerror(errno)); + error.FormatErrno("Failed to open %s", + so->device); return false; } @@ -136,21 +130,19 @@ solaris_output_open(struct audio_output *ao, struct audio_format *audio_format, ret = ioctl(so->fd, AUDIO_GETINFO, &info); if (ret < 0) { - g_set_error(error, solaris_output_quark(), errno, - "AUDIO_GETINFO failed: %s", g_strerror(errno)); + error.SetErrno("AUDIO_GETINFO failed"); close(so->fd); return false; } - info.play.sample_rate = audio_format->sample_rate; - info.play.channels = audio_format->channels; + info.play.sample_rate = audio_format.sample_rate; + info.play.channels = audio_format.channels; info.play.precision = 16; info.play.encoding = AUDIO_ENCODING_LINEAR; ret = ioctl(so->fd, AUDIO_SETINFO, &info); if (ret < 0) { - g_set_error(error, solaris_output_quark(), errno, - "AUDIO_SETINFO failed: %s", g_strerror(errno)); + error.SetErrno("AUDIO_SETINFO failed"); close(so->fd); return false; } @@ -161,22 +153,21 @@ solaris_output_open(struct audio_output *ao, struct audio_format *audio_format, static void solaris_output_close(struct audio_output *ao) { - struct solaris_output *so = (struct solaris_output *)ao; + SolarisOutput *so = (SolarisOutput *)ao; close(so->fd); } static size_t solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) + Error &error) { - struct solaris_output *so = (struct solaris_output *)ao; + SolarisOutput *so = (SolarisOutput *)ao; ssize_t nbytes; nbytes = write(so->fd, chunk, size); if (nbytes <= 0) { - g_set_error(error, solaris_output_quark(), errno, - "Write failed: %s", g_strerror(errno)); + error.SetErrno("Write failed"); return 0; } @@ -186,18 +177,25 @@ solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, static void solaris_output_cancel(struct audio_output *ao) { - struct solaris_output *so = (struct solaris_output *)ao; + SolarisOutput *so = (SolarisOutput *)ao; ioctl(so->fd, I_FLUSH); } const struct audio_output_plugin solaris_output_plugin = { - .name = "solaris", - .test_default_device = solaris_output_test_default_device, - .init = solaris_output_init, - .finish = solaris_output_finish, - .open = solaris_output_open, - .close = solaris_output_close, - .play = solaris_output_play, - .cancel = solaris_output_cancel, + "solaris", + solaris_output_test_default_device, + solaris_output_init, + solaris_output_finish, + nullptr, + nullptr, + solaris_output_open, + solaris_output_close, + nullptr, + nullptr, + solaris_output_play, + nullptr, + solaris_output_cancel, + nullptr, + nullptr, }; diff --git a/src/output/solaris_output_plugin.h b/src/output/SolarisOutputPlugin.hxx index 600aea8c2..d0fbd32c8 100644 --- a/src/output/solaris_output_plugin.h +++ b/src/output/SolarisOutputPlugin.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_SOLARIS_OUTPUT_PLUGIN_H -#define MPD_SOLARIS_OUTPUT_PLUGIN_H +#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX +#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX extern const struct audio_output_plugin solaris_output_plugin; diff --git a/src/output/winmm_output_plugin.c b/src/output/WinmmOutputPlugin.cxx index 4d95834b9..d3f74dd44 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,25 @@ */ #include "config.h" -#include "winmm_output_plugin.h" -#include "output_api.h" -#include "pcm_buffer.h" -#include "mixer_list.h" -#include "winmm_output_plugin.h" +#include "WinmmOutputPlugin.hxx" +#include "OutputAPI.hxx" +#include "pcm/PcmBuffer.hxx" +#include "MixerList.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <glib.h> #include <stdlib.h> #include <string.h> -#include <windows.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "winmm_output" -struct winmm_buffer { - struct pcm_buffer buffer; +struct WinmmBuffer { + PcmBuffer buffer; WAVEHDR hdr; }; -struct winmm_output { +struct WinmmOutput { struct audio_output base; UINT device_id; @@ -49,21 +48,14 @@ struct winmm_output { */ HANDLE event; - struct winmm_buffer buffers[8]; + WinmmBuffer buffers[8]; unsigned next_buffer; }; -/** - * The quark used for GError.domain. - */ -static inline GQuark -winmm_output_quark(void) -{ - return g_quark_from_static_string("winmm_output"); -} +static constexpr Domain winmm_output_domain("winmm_output"); HWAVEOUT -winmm_output_get_handle(struct winmm_output* output) +winmm_output_get_handle(WinmmOutput *output) { return output->handle; } @@ -75,10 +67,10 @@ winmm_output_test_default_device(void) } static bool -get_device_id(const char *device_name, UINT *device_id, GError **error_r) +get_device_id(const char *device_name, UINT *device_id, Error &error) { /* if device is not specified use wave mapper */ - if (device_name == NULL) { + if (device_name == nullptr) { *device_id = WAVE_MAPPER; return true; } @@ -110,25 +102,25 @@ get_device_id(const char *device_name, UINT *device_id, GError **error_r) } fail: - g_set_error(error_r, winmm_output_quark(), 0, - "device \"%s\" is not found", device_name); + error.Format(winmm_output_domain, + "device \"%s\" is not found", device_name); return false; } static struct audio_output * -winmm_output_init(const struct config_param *param, GError **error_r) +winmm_output_init(const config_param ¶m, Error &error) { - struct winmm_output *wo = g_new(struct winmm_output, 1); - if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) { + WinmmOutput *wo = new WinmmOutput(); + if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) { g_free(wo); - return NULL; + return nullptr; } - const char *device = config_get_block_string(param, "device", NULL); - if (!get_device_id(device, &wo->device_id, error_r)) { + const char *device = param.GetBlockValue("device"); + if (!get_device_id(device, &wo->device_id, error)) { ao_base_finish(&wo->base); g_free(wo); - return NULL; + return nullptr; } return &wo->base; @@ -137,62 +129,61 @@ 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) +winmm_output_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error) { - struct winmm_output *wo = (struct winmm_output *)ao; + WinmmOutput *wo = (WinmmOutput *)ao; - wo->event = CreateEvent(NULL, false, false, NULL); - if (wo->event == NULL) { - g_set_error(error_r, winmm_output_quark(), 0, - "CreateEvent() failed"); + wo->event = CreateEvent(nullptr, false, false, nullptr); + if (wo->event == nullptr) { + error.Set(winmm_output_domain, "CreateEvent() failed"); return false; } - switch (audio_format->format) { - case SAMPLE_FORMAT_S8: - case SAMPLE_FORMAT_S16: + switch (audio_format.format) { + case SampleFormat::S8: + case SampleFormat::S16: break; - case SAMPLE_FORMAT_S24_P32: - case SAMPLE_FORMAT_S32: - case SAMPLE_FORMAT_UNDEFINED: + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + case SampleFormat::UNDEFINED: /* we havn't tested formats other than S16 */ - audio_format->format = SAMPLE_FORMAT_S16; + audio_format.format = SampleFormat::S16; break; } - if (audio_format->channels > 2) + if (audio_format.channels > 2) /* same here: more than stereo was not tested */ - audio_format->channels = 2; + audio_format.channels = 2; WAVEFORMATEX format; format.wFormatTag = WAVE_FORMAT_PCM; - format.nChannels = audio_format->channels; - format.nSamplesPerSec = audio_format->sample_rate; - format.nBlockAlign = audio_format_frame_size(audio_format); + format.nChannels = audio_format.channels; + format.nSamplesPerSec = audio_format.sample_rate; + format.nBlockAlign = audio_format.GetFrameSize(); format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - format.wBitsPerSample = audio_format_sample_size(audio_format) * 8; + format.wBitsPerSample = audio_format.GetSampleSize() * 8; format.cbSize = 0; MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format, (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); if (result != MMSYSERR_NOERROR) { CloseHandle(wo->event); - g_set_error(error_r, winmm_output_quark(), result, - "waveOutOpen() failed"); + error.Set(winmm_output_domain, "waveOutOpen() failed"); return false; } for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { - pcm_buffer_init(&wo->buffers[i].buffer); memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr)); } @@ -204,10 +195,10 @@ 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); + wo->buffers[i].buffer.Clear(); waveOutClose(wo->handle); @@ -218,24 +209,24 @@ 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) + Error &error) { - void *dest = pcm_buffer_get(&buffer->buffer, size); - assert(dest != NULL); + void *dest = buffer->buffer.Get(size); + 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, sizeof(buffer->hdr)); if (result != MMSYSERR_NOERROR) { - g_set_error(error_r, winmm_output_quark(), result, - "waveOutPrepareHeader() failed"); + error.Set(winmm_output_domain, result, + "waveOutPrepareHeader() failed"); return false; } @@ -246,8 +237,8 @@ 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, - GError **error_r) +winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, + Error &error) { if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) /* already finished */ @@ -260,8 +251,8 @@ winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, if (result == MMSYSERR_NOERROR) return true; else if (result != WAVERR_STILLPLAYING) { - g_set_error(error_r, winmm_output_quark(), result, - "waveOutUnprepareHeader() failed"); + error.Set(winmm_output_domain, result, + "waveOutUnprepareHeader() failed"); return false; } @@ -271,14 +262,14 @@ 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) +winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error) { - 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]; - if (!winmm_drain_buffer(wo, buffer, error_r) || - !winmm_set_buffer(wo, buffer, chunk, size, error_r)) + WinmmBuffer *buffer = &wo->buffers[wo->next_buffer]; + if (!winmm_drain_buffer(wo, buffer, error) || + !winmm_set_buffer(wo, buffer, chunk, size, error)) return 0; /* enqueue the buffer */ @@ -287,8 +278,8 @@ winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GErro if (result != MMSYSERR_NOERROR) { waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); - g_set_error(error_r, winmm_output_quark(), result, - "waveOutWrite() failed"); + error.Set(winmm_output_domain, result, + "waveOutWrite() failed"); return 0; } @@ -300,26 +291,26 @@ 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, Error &error) { for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i) - if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) return false; for (unsigned i = 0; i < wo->next_buffer; ++i) - if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) return false; return true; } 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 +319,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, IgnoreError())) 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/ffado_output_plugin.c b/src/output/ffado_output_plugin.c deleted file mode 100644 index ba239a4ad..000000000 --- a/src/output/ffado_output_plugin.c +++ /dev/null @@ -1,359 +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. - */ - -/* - * Warning: this plugin was not tested successfully. I just couldn't - * keep libffado2 from crashing. Use at your own risk. - * - * For details, see my Debian bug reports: - * - * http://bugs.debian.org/601657 - * http://bugs.debian.org/601659 - * http://bugs.debian.org/601663 - * - */ - -#include "config.h" -#include "ffado_output_plugin.h" -#include "output_api.h" -#include "timer.h" - -#include <glib.h> -#include <assert.h> - -#include <libffado/ffado.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ffado" - -enum { - MAX_STREAMS = 8, -}; - -struct mpd_ffado_stream { - /** libffado's stream number */ - int number; - - float *buffer; -}; - -struct mpd_ffado_device { - struct audio_output base; - - char *device_name; - int verbose; - unsigned period_size, nb_buffers; - - ffado_device_t *dev; - - /** - * The current sample position inside the stream buffers. New - * samples get appended at this position on all streams at the - * same time. When the buffers are full - * (buffer_position==period_size), - * ffado_streaming_transfer_playback_buffers() gets called to - * hand them over to libffado. - */ - unsigned buffer_position; - - /** - * The number of streams which are really used by MPD. - */ - int num_streams; - struct mpd_ffado_stream streams[MAX_STREAMS]; -}; - -static inline GQuark -ffado_output_quark(void) -{ - return g_quark_from_static_string("ffado_output"); -} - -static struct audio_output * -ffado_init(const struct config_param *param, - GError **error_r) -{ - g_debug("using libffado version %s, API=%d", - ffado_get_version(), ffado_get_api_version()); - - struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1); - if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) { - g_free(fd); - return NULL; - } - - fd->device_name = config_dup_block_string(param, "device", NULL); - fd->verbose = config_get_block_unsigned(param, "verbose", 0); - - fd->period_size = config_get_block_unsigned(param, "period_size", - 1024); - if (fd->period_size == 0 || fd->period_size > 1024 * 1024) { - ao_base_finish(&fd->base); - g_set_error(error_r, ffado_output_quark(), 0, - "invalid period_size setting"); - return false; - } - - fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3); - if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) { - ao_base_finish(&fd->base); - g_set_error(error_r, ffado_output_quark(), 0, - "invalid nb_buffers setting"); - return false; - } - - return &fd->base; -} - -static void -ffado_finish(struct audio_output *ao) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - g_free(fd->device_name); - ao_base_finish(&fd->base); - g_free(fd); -} - -static bool -ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream, - GError **error_r) -{ - char *buffer = (char *)stream->buffer; - if (ffado_streaming_set_playback_stream_buffer(dev, stream->number, - buffer) != 0) { - g_set_error(error_r, ffado_output_quark(), 0, - "failed to configure stream buffer"); - return false; - } - - if (ffado_streaming_playback_stream_onoff(dev, stream->number, - 1) != 0) { - g_set_error(error_r, ffado_output_quark(), 0, - "failed to disable stream"); - return false; - } - - return true; -} - -static bool -ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format, - GError **error_r) -{ - assert(fd != NULL); - assert(fd->dev != NULL); - assert(audio_format->channels <= MAX_STREAMS); - - if (ffado_streaming_set_audio_datatype(fd->dev, - ffado_audio_datatype_float) != 0) { - g_set_error(error_r, ffado_output_quark(), 0, - "ffado_streaming_set_audio_datatype() failed"); - return false; - } - - int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev); - if (num_streams < 0) { - g_set_error(error_r, ffado_output_quark(), 0, - "ffado_streaming_get_nb_playback_streams() failed"); - return false; - } - - g_debug("there are %d playback streams", num_streams); - - fd->num_streams = 0; - for (int i = 0; i < num_streams; ++i) { - char name[256]; - ffado_streaming_get_playback_stream_name(fd->dev, i, name, - sizeof(name) - 1); - - ffado_streaming_stream_type type = - ffado_streaming_get_playback_stream_type(fd->dev, i); - if (type != ffado_stream_type_audio) { - g_debug("stream %d name='%s': not an audio stream", - i, name); - continue; - } - - if (fd->num_streams >= audio_format->channels) { - g_debug("stream %d name='%s': ignoring", - i, name); - continue; - } - - g_debug("stream %d name='%s'", i, name); - - struct mpd_ffado_stream *stream = - &fd->streams[fd->num_streams++]; - - stream->number = i; - - /* allocated buffer is zeroed = silence */ - stream->buffer = g_new0(float, fd->period_size); - - if (!ffado_configure_stream(fd->dev, stream, error_r)) - return false; - } - - if (!audio_valid_channel_count(fd->num_streams)) { - g_set_error(error_r, ffado_output_quark(), 0, - "invalid channel count from libffado: %u", - audio_format->channels); - return false; - } - - g_debug("configured %d audio streams", fd->num_streams); - - if (ffado_streaming_prepare(fd->dev) != 0) { - g_set_error(error_r, ffado_output_quark(), 0, - "ffado_streaming_prepare() failed"); - return false; - } - - if (ffado_streaming_start(fd->dev) != 0) { - g_set_error(error_r, ffado_output_quark(), 0, - "ffado_streaming_start() failed"); - return false; - } - - audio_format->channels = fd->num_streams; - return true; -} - -static bool -ffado_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - /* will be converted to floating point, choose best input - format */ - audio_format->format = SAMPLE_FORMAT_S24_P32; - - ffado_device_info_t device_info; - memset(&device_info, 0, sizeof(device_info)); - if (fd->device_name != NULL) { - device_info.nb_device_spec_strings = 1; - device_info.device_spec_strings = &fd->device_name; - } - - ffado_options_t options; - memset(&options, 0, sizeof(options)); - options.sample_rate = audio_format->sample_rate; - options.period_size = fd->period_size; - options.nb_buffers = fd->nb_buffers; - options.verbose = fd->verbose; - - fd->dev = ffado_streaming_init(device_info, options); - if (fd->dev == NULL) { - g_set_error(error_r, ffado_output_quark(), 0, - "ffado_streaming_init() failed"); - return false; - } - - if (!ffado_configure(fd, audio_format, error_r)) { - ffado_streaming_finish(fd->dev); - - for (int i = 0; i < fd->num_streams; ++i) { - struct mpd_ffado_stream *stream = &fd->streams[i]; - g_free(stream->buffer); - } - - return false; - } - - fd->buffer_position = 0; - - return true; -} - -static void -ffado_close(struct audio_output *ao) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - ffado_streaming_stop(fd->dev); - ffado_streaming_finish(fd->dev); - - for (int i = 0; i < fd->num_streams; ++i) { - struct mpd_ffado_stream *stream = &fd->streams[i]; - g_free(stream->buffer); - } -} - -static size_t -ffado_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; - - /* wait for prefious buffer to finish (if it was full) */ - - if (fd->buffer_position >= fd->period_size) { - switch (ffado_streaming_wait(fd->dev)) { - case ffado_wait_ok: - case ffado_wait_xrun: - break; - - default: - g_set_error(error_r, ffado_output_quark(), 0, - "ffado_streaming_wait() failed"); - return 0; - } - - fd->buffer_position = 0; - } - - /* copy samples to stream buffers, non-interleaved */ - - const int32_t *p = chunk; - unsigned num_frames = size / sizeof(*p) / fd->num_streams; - if (num_frames > fd->period_size - fd->buffer_position) - num_frames = fd->period_size - fd->buffer_position; - - for (unsigned i = num_frames; i > 0; --i) { - for (int stream = 0; stream < fd->num_streams; ++stream) - fd->streams[stream].buffer[fd->buffer_position] = - *p++ / (float)(1 << 23); - ++fd->buffer_position; - } - - /* if buffer full, transfer to device */ - - if (fd->buffer_position >= fd->period_size && - /* libffado documentation says this function returns -1 on - error, but that is a lie - it returns a boolean value, - and "false" means error */ - !ffado_streaming_transfer_playback_buffers(fd->dev)) { - g_set_error(error_r, ffado_output_quark(), 0, - "ffado_streaming_transfer_playback_buffers() failed"); - return 0; - } - - return num_frames * sizeof(*p) * fd->num_streams; -} - -const struct audio_output_plugin ffado_output_plugin = { - .name = "ffado", - .init = ffado_init, - .finish = ffado_finish, - .open = ffado_open, - .close = ffado_close, - .play = ffado_play, -}; diff --git a/src/output/ffado_output_plugin.h b/src/output/ffado_output_plugin.h deleted file mode 100644 index 4dde01859..000000000 --- a/src/output/ffado_output_plugin.h +++ /dev/null @@ -1,25 +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_FFADO_OUTPUT_PLUGIN_H -#define MPD_FFADO_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin ffado_output_plugin; - -#endif diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c deleted file mode 100644 index 022be0b4a..000000000 --- a/src/output/fifo_output_plugin.c +++ /dev/null @@ -1,315 +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 "fifo_output_plugin.h" -#include "output_api.h" -#include "utils.h" -#include "timer.h" -#include "fd_util.h" -#include "open.h" - -#include <glib.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <errno.h> -#include <string.h> -#include <unistd.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "fifo" - -#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ - -struct fifo_data { - struct audio_output base; - - char *path; - int input; - int output; - bool created; - struct timer *timer; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -fifo_output_quark(void) -{ - return g_quark_from_static_string("fifo_output"); -} - -static struct fifo_data *fifo_data_new(void) -{ - struct fifo_data *ret; - - ret = g_new(struct fifo_data, 1); - - ret->path = NULL; - ret->input = -1; - ret->output = -1; - ret->created = false; - - return ret; -} - -static void fifo_data_free(struct fifo_data *fd) -{ - g_free(fd->path); - g_free(fd); -} - -static void fifo_delete(struct fifo_data *fd) -{ - g_debug("Removing FIFO \"%s\"", fd->path); - - if (unlink(fd->path) < 0) { - g_warning("Could not remove FIFO \"%s\": %s", - fd->path, g_strerror(errno)); - return; - } - - fd->created = false; -} - -static void -fifo_close(struct fifo_data *fd) -{ - struct stat st; - - if (fd->input >= 0) { - close(fd->input); - fd->input = -1; - } - - if (fd->output >= 0) { - close(fd->output); - fd->output = -1; - } - - if (fd->created && (stat(fd->path, &st) == 0)) - fifo_delete(fd); -} - -static bool -fifo_make(struct fifo_data *fd, GError **error) -{ - if (mkfifo(fd->path, 0666) < 0) { - g_set_error(error, fifo_output_quark(), errno, - "Couldn't create FIFO \"%s\": %s", - fd->path, g_strerror(errno)); - return false; - } - - fd->created = true; - - return true; -} - -static bool -fifo_check(struct fifo_data *fd, GError **error) -{ - struct stat st; - - if (stat(fd->path, &st) < 0) { - if (errno == ENOENT) { - /* Path doesn't exist */ - return fifo_make(fd, error); - } - - g_set_error(error, fifo_output_quark(), errno, - "Failed to stat FIFO \"%s\": %s", - fd->path, g_strerror(errno)); - return false; - } - - if (!S_ISFIFO(st.st_mode)) { - g_set_error(error, fifo_output_quark(), 0, - "\"%s\" already exists, but is not a FIFO", - fd->path); - return false; - } - - return true; -} - -static bool -fifo_open(struct fifo_data *fd, GError **error) -{ - if (!fifo_check(fd, error)) - return false; - - fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK|O_BINARY, 0); - if (fd->input < 0) { - g_set_error(error, fifo_output_quark(), errno, - "Could not open FIFO \"%s\" for reading: %s", - fd->path, g_strerror(errno)); - fifo_close(fd); - return false; - } - - fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK|O_BINARY, 0); - if (fd->output < 0) { - g_set_error(error, fifo_output_quark(), errno, - "Could not open FIFO \"%s\" for writing: %s", - fd->path, g_strerror(errno)); - fifo_close(fd); - return false; - } - - return true; -} - -static struct audio_output * -fifo_output_init(const struct config_param *param, - GError **error_r) -{ - struct fifo_data *fd; - - GError *error = NULL; - char *path = config_dup_block_path(param, "path", &error); - if (!path) { - if (error != NULL) - g_propagate_error(error_r, error); - else - g_set_error(error_r, fifo_output_quark(), 0, - "No \"path\" parameter specified"); - return NULL; - } - - fd = fifo_data_new(); - fd->path = path; - - if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) { - fifo_data_free(fd); - return NULL; - } - - if (!fifo_open(fd, error_r)) { - ao_base_finish(&fd->base); - fifo_data_free(fd); - return NULL; - } - - return &fd->base; -} - -static void -fifo_output_finish(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - fifo_close(fd); - ao_base_finish(&fd->base); - fifo_data_free(fd); -} - -static bool -fifo_output_open(struct audio_output *ao, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - fd->timer = timer_new(audio_format); - - return true; -} - -static void -fifo_output_close(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - timer_free(fd->timer); -} - -static void -fifo_output_cancel(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - char buf[FIFO_BUFFER_SIZE]; - int bytes = 1; - - timer_reset(fd->timer); - - while (bytes > 0 && errno != EINTR) - bytes = read(fd->input, buf, FIFO_BUFFER_SIZE); - - if (bytes < 0 && errno != EAGAIN) { - g_warning("Flush of FIFO \"%s\" failed: %s", - fd->path, g_strerror(errno)); - } -} - -static unsigned -fifo_output_delay(struct audio_output *ao) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - - return fd->timer->started - ? timer_delay(fd->timer) - : 0; -} - -static size_t -fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct fifo_data *fd = (struct fifo_data *)ao; - ssize_t bytes; - - if (!fd->timer->started) - timer_start(fd->timer); - timer_add(fd->timer, size); - - while (true) { - bytes = write(fd->output, chunk, size); - if (bytes > 0) - return (size_t)bytes; - - if (bytes < 0) { - switch (errno) { - case EAGAIN: - /* The pipe is full, so empty it */ - fifo_output_cancel(&fd->base); - continue; - case EINTR: - continue; - } - - g_set_error(error, fifo_output_quark(), errno, - "Failed to write to FIFO %s: %s", - fd->path, g_strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin fifo_output_plugin = { - .name = "fifo", - .init = fifo_output_init, - .finish = fifo_output_finish, - .open = fifo_output_open, - .close = fifo_output_close, - .delay = fifo_output_delay, - .play = fifo_output_play, - .cancel = fifo_output_cancel, -}; 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/mvp_output_plugin.c b/src/output/mvp_output_plugin.c deleted file mode 100644 index 37e0f7c93..000000000 --- a/src/output/mvp_output_plugin.c +++ /dev/null @@ -1,344 +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. - */ - -/* - * Media MVP audio output based on code from MVPMC project: - * http://mvpmc.sourceforge.net/ - */ - -#include "config.h" -#include "mvp_output_plugin.h" -#include "output_api.h" -#include "fd_util.h" - -#include <glib.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mvp" - -typedef struct { - unsigned long dsp_status; - unsigned long stream_decode_type; - unsigned long sample_rate; - unsigned long bit_rate; - unsigned long raw[64 / sizeof(unsigned long)]; -} aud_status_t; - -#define MVP_SET_AUD_STOP _IOW('a',1,int) -#define MVP_SET_AUD_PLAY _IOW('a',2,int) -#define MVP_SET_AUD_PAUSE _IOW('a',3,int) -#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int) -#define MVP_SET_AUD_SRC _IOW('a',5,int) -#define MVP_SET_AUD_MUTE _IOW('a',6,int) -#define MVP_SET_AUD_BYPASS _IOW('a',8,int) -#define MVP_SET_AUD_CHANNEL _IOW('a',9,int) -#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t) -#define MVP_SET_AUD_VOLUME _IOW('a',13,int) -#define MVP_GET_AUD_VOLUME _IOR('a',14,int) -#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int) -#define MVP_SET_AUD_FORMAT _IOW('a',16,int) -#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*) -#define MVP_SET_AUD_STC _IOW('a',22,long long int *) -#define MVP_SET_AUD_SYNC _IOW('a',23,int) -#define MVP_SET_AUD_END_STREAM _IOW('a',25,int) -#define MVP_SET_AUD_RESET _IOW('a',26,int) -#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int) -#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) - -struct mvp_data { - struct audio_output base; - - struct audio_format audio_format; - int fd; -}; - -static const unsigned mvp_sample_rates[][3] = { - {9, 8000, 32000}, - {10, 11025, 44100}, - {11, 12000, 48000}, - {1, 16000, 32000}, - {2, 22050, 44100}, - {3, 24000, 48000}, - {5, 32000, 32000}, - {0, 44100, 44100}, - {7, 48000, 48000}, - {13, 64000, 32000}, - {14, 88200, 44100}, - {15, 96000, 48000} -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -mvp_output_quark(void) -{ - return g_quark_from_static_string("mvp_output"); -} - -/** - * Translate a sample rate to a MVP sample rate. - * - * @param sample_rate the sample rate in Hz - */ -static unsigned -mvp_find_sample_rate(unsigned sample_rate) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(mvp_sample_rates); ++i) - if (mvp_sample_rates[i][1] == sample_rate) - return mvp_sample_rates[i][0]; - - return (unsigned)-1; -} - -static bool -mvp_output_test_default_device(void) -{ - int fd; - - fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0); - - if (fd >= 0) { - close(fd); - return true; - } - - g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n", - g_strerror(errno)); - - return false; -} - -static struct audio_output * -mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error) -{ - struct mvp_data *md = g_new(struct mvp_data, 1); - - if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) { - g_free(md); - return NULL; - } - - md->fd = -1; - - return &md->base; -} - -static void -mvp_output_finish(struct audio_output *ao) -{ - struct mvp_data *md = (struct mvp_data *)ao; - ao_base_finish(&md->base); - g_free(md); -} - -static bool -mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, - GError **error) -{ - unsigned mix[5]; - - switch (audio_format->channels) { - case 1: - mix[0] = 1; - break; - - case 2: - mix[0] = 0; - break; - - default: - g_debug("unsupported channel count %u - falling back to stereo", - audio_format->channels); - audio_format->channels = 2; - mix[0] = 0; - break; - } - - /* 0,1=24bit(24) , 2,3=16bit */ - switch (audio_format->format) { - case SAMPLE_FORMAT_S16: - mix[1] = 2; - break; - - case SAMPLE_FORMAT_S24_P32: - mix[1] = 0; - break; - - default: - g_debug("unsupported sample format %s - falling back to 16 bit", - sample_format_to_string(audio_format->format)); - audio_format->format = SAMPLE_FORMAT_S16; - mix[1] = 2; - break; - } - - mix[3] = 0; /* stream type? */ - mix[4] = G_BYTE_ORDER == G_LITTLE_ENDIAN; - - /* - * if there is an exact match for the frequency, use it. - */ - mix[2] = mvp_find_sample_rate(audio_format->sample_rate); - if (mix[2] == (unsigned)-1) { - g_set_error(error, mvp_output_quark(), 0, - "Can not find suitable output frequency for %u", - audio_format->sample_rate); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio format"); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio sync"); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio play mode"); - return false; - } - - return true; -} - -static bool -mvp_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct mvp_data *md = (struct mvp_data *)ao; - long long int stc = 0; - int mix[5] = { 0, 2, 7, 1, 0 }; - bool success; - - md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0); - if (md->fd < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error opening /dev/adec_pcm: %s", - g_strerror(errno)); - return false; - } - if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio source: %s", - g_strerror(errno)); - return false; - } - if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio streamtype: %s", - g_strerror(errno)); - return false; - } - if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio format: %s", - g_strerror(errno)); - return false; - } - ioctl(md->fd, MVP_SET_AUD_STC, &stc); - if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error setting audio streamtype: %s", - g_strerror(errno)); - return false; - } - - success = mvp_set_pcm_params(md, audio_format, error); - if (!success) - return false; - - md->audio_format = *audio_format; - return true; -} - -static void mvp_output_close(struct audio_output *ao) -{ - struct mvp_data *md = (struct mvp_data *)ao; - if (md->fd >= 0) - close(md->fd); - md->fd = -1; -} - -static void mvp_output_cancel(struct audio_output *ao) -{ - struct mvp_data *md = (struct mvp_data *)ao; - if (md->fd >= 0) { - ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); - close(md->fd); - md->fd = -1; - } -} - -static size_t -mvp_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct mvp_data *md = (struct mvp_data *)ao; - ssize_t ret; - - /* reopen the device since it was closed by dropBufferedAudio */ - if (md->fd < 0) { - bool success; - - success = mvp_output_open(ao, &md->audio_format, error); - if (!success) - return 0; - } - - while (true) { - ret = write(md->fd, chunk, size); - if (ret > 0) - return (size_t)ret; - - if (ret < 0) { - if (errno == EINTR) - continue; - - g_set_error(error, mvp_output_quark(), errno, - "Failed to write: %s", g_strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin mvp_output_plugin = { - .name = "mvp", - .test_default_device = mvp_output_test_default_device, - .init = mvp_output_init, - .finish = mvp_output_finish, - .open = mvp_output_open, - .close = mvp_output_close, - .play = mvp_output_play, - .cancel = mvp_output_cancel, -}; diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h deleted file mode 100644 index e403de2b7..000000000 --- a/src/output/mvp_output_plugin.h +++ /dev/null @@ -1,25 +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_MVP_OUTPUT_PLUGIN_H -#define MPD_MVP_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin mvp_output_plugin; - -#endif diff --git a/src/output/null_output_plugin.c b/src/output/null_output_plugin.c deleted file mode 100644 index 9d7588fff..000000000 --- a/src/output/null_output_plugin.c +++ /dev/null @@ -1,129 +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 "null_output_plugin.h" -#include "output_api.h" -#include "timer.h" - -#include <glib.h> - -#include <assert.h> - -struct null_data { - struct audio_output base; - - bool sync; - - struct timer *timer; -}; - -static struct audio_output * -null_init(const struct config_param *param, GError **error_r) -{ - struct null_data *nd = g_new(struct null_data, 1); - - if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { - g_free(nd); - return NULL; - } - - nd->sync = config_get_block_bool(param, "sync", true); - - return &nd->base; -} - -static void -null_finish(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - ao_base_finish(&nd->base); - g_free(nd); -} - -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; - - if (nd->sync) - nd->timer = timer_new(audio_format); - - return true; -} - -static void -null_close(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - if (nd->sync) - timer_free(nd->timer); -} - -static unsigned -null_delay(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - return nd->sync && nd->timer->started - ? timer_delay(nd->timer) - : 0; -} - -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; - struct timer *timer = nd->timer; - - if (!nd->sync) - return size; - - if (!timer->started) - timer_start(timer); - timer_add(timer, size); - - return size; -} - -static void -null_cancel(struct audio_output *ao) -{ - struct null_data *nd = (struct null_data *)ao; - - if (!nd->sync) - return; - - timer_reset(nd->timer); -} - -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, -}; diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c deleted file mode 100644 index 90c5a5331..000000000 --- a/src/output/pipe_output_plugin.c +++ /dev/null @@ -1,121 +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 "pipe_output_plugin.h" -#include "output_api.h" - -#include <stdio.h> -#include <errno.h> - -struct pipe_output { - struct audio_output base; - - char *cmd; - FILE *fh; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -pipe_output_quark(void) -{ - return g_quark_from_static_string("pipe_output"); -} - -static struct audio_output * -pipe_output_init(const struct config_param *param, - GError **error) -{ - struct pipe_output *pd = g_new(struct pipe_output, 1); - - if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) { - g_free(pd); - return NULL; - } - - pd->cmd = config_dup_block_string(param, "command", NULL); - if (pd->cmd == NULL) { - g_set_error(error, pipe_output_quark(), 0, - "No \"command\" parameter specified"); - return NULL; - } - - return &pd->base; -} - -static void -pipe_output_finish(struct audio_output *ao) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - - g_free(pd->cmd); - ao_base_finish(&pd->base); - g_free(pd); -} - -static bool -pipe_output_open(struct audio_output *ao, - G_GNUC_UNUSED struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - - pd->fh = popen(pd->cmd, "w"); - if (pd->fh == NULL) { - g_set_error(error, pipe_output_quark(), errno, - "Error opening pipe \"%s\": %s", - pd->cmd, g_strerror(errno)); - return false; - } - - return true; -} - -static void -pipe_output_close(struct audio_output *ao) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - - pclose(pd->fh); -} - -static size_t -pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) -{ - struct pipe_output *pd = (struct pipe_output *)ao; - size_t ret; - - ret = fwrite(chunk, 1, size, pd->fh); - if (ret == 0) - g_set_error(error, pipe_output_quark(), errno, - "Write error on pipe: %s", g_strerror(errno)); - - return ret; -} - -const struct audio_output_plugin pipe_output_plugin = { - .name = "pipe", - .init = pipe_output_init, - .finish = pipe_output_finish, - .open = pipe_output_open, - .close = pipe_output_close, - .play = pipe_output_play, -}; diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c deleted file mode 100644 index b84cb244c..000000000 --- a/src/output/recorder_output_plugin.c +++ /dev/null @@ -1,251 +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 "recorder_output_plugin.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "fd_util.h" -#include "open.h" - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "recorder" - -struct recorder_output { - struct audio_output base; - - /** - * The configured encoder plugin. - */ - struct encoder *encoder; - - /** - * The destination file name. - */ - const char *path; - - /** - * The destination file descriptor. - */ - int fd; - - /** - * The buffer for encoder_read(). - */ - char buffer[32768]; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -recorder_output_quark(void) -{ - return g_quark_from_static_string("recorder_output"); -} - -static struct audio_output * -recorder_output_init(const struct config_param *param, GError **error_r) -{ - struct recorder_output *recorder = g_new(struct recorder_output, 1); - if (!ao_base_init(&recorder->base, &recorder_output_plugin, param, - error_r)) { - g_free(recorder); - return NULL; - } - - /* read configuration */ - - 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, recorder_output_quark(), 0, - "No such encoder: %s", encoder_name); - goto failure; - } - - recorder->path = config_get_block_string(param, "path", NULL); - if (recorder->path == NULL) { - g_set_error(error_r, recorder_output_quark(), 0, - "'path' not configured"); - goto failure; - } - - /* initialize encoder */ - - recorder->encoder = encoder_init(encoder_plugin, param, error_r); - if (recorder->encoder == NULL) - goto failure; - - return &recorder->base; - -failure: - ao_base_finish(&recorder->base); - g_free(recorder); - return NULL; -} - -static void -recorder_output_finish(struct audio_output *ao) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - encoder_finish(recorder->encoder); - ao_base_finish(&recorder->base); - g_free(recorder); -} - -static bool -recorder_write_to_file(struct recorder_output *recorder, - const void *_data, size_t length, - GError **error_r) -{ - assert(length > 0); - - const int fd = recorder->fd; - - const uint8_t *data = (const uint8_t *)_data, *end = data + length; - - while (true) { - ssize_t nbytes = write(fd, data, end - data); - if (nbytes > 0) { - data += nbytes; - if (data == end) - return true; - } else if (nbytes == 0) { - /* shouldn't happen for files */ - g_set_error(error_r, recorder_output_quark(), 0, - "write() returned 0"); - return false; - } else if (errno != EINTR) { - g_set_error(error_r, recorder_output_quark(), 0, - "Failed to write to '%s': %s", - recorder->path, g_strerror(errno)); - return false; - } - } -} - -/** - * Writes pending data from the encoder to the output file. - */ -static bool -recorder_output_encoder_to_file(struct recorder_output *recorder, - GError **error_r) -{ - assert(recorder->fd >= 0); - - while (true) { - /* read from the encoder */ - - size_t size = encoder_read(recorder->encoder, recorder->buffer, - sizeof(recorder->buffer)); - if (size == 0) - return true; - - /* write everything into the file */ - - if (!recorder_write_to_file(recorder, recorder->buffer, size, - error_r)) - return false; - } -} - -static bool -recorder_output_open(struct audio_output *ao, - struct audio_format *audio_format, - GError **error_r) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - /* create the output file */ - - recorder->fd = open_cloexec(recorder->path, - O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, - 0666); - if (recorder->fd < 0) { - g_set_error(error_r, recorder_output_quark(), 0, - "Failed to create '%s': %s", - recorder->path, g_strerror(errno)); - return false; - } - - /* open the encoder */ - - if (!encoder_open(recorder->encoder, audio_format, error_r)) { - close(recorder->fd); - unlink(recorder->path); - return false; - } - - if (!recorder_output_encoder_to_file(recorder, error_r)) { - encoder_close(recorder->encoder); - close(recorder->fd); - unlink(recorder->path); - return false; - } - - return true; -} - -static void -recorder_output_close(struct audio_output *ao) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - /* flush the encoder and write the rest to the file */ - - if (encoder_end(recorder->encoder, NULL)) - recorder_output_encoder_to_file(recorder, NULL); - - /* now really close everything */ - - encoder_close(recorder->encoder); - - close(recorder->fd); -} - -static size_t -recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct recorder_output *recorder = (struct recorder_output *)ao; - - return encoder_write(recorder->encoder, chunk, size, error_r) && - recorder_output_encoder_to_file(recorder, error_r) - ? size : 0; -} - -const struct audio_output_plugin recorder_output_plugin = { - .name = "recorder", - .init = recorder_output_init, - .finish = recorder_output_finish, - .open = recorder_output_open, - .close = recorder_output_close, - .play = recorder_output_play, -}; diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c deleted file mode 100644 index 56456a0ea..000000000 --- a/src/output/shout_output_plugin.c +++ /dev/null @@ -1,555 +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 "shout_output_plugin.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "mpd_error.h" - -#include <shout/shout.h> -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <stdio.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "shout" - -#define DEFAULT_CONN_TIMEOUT 2 - -struct shout_data { - struct audio_output base; - - shout_t *shout_conn; - shout_metadata_t *shout_meta; - - struct encoder *encoder; - - float quality; - int bitrate; - - int timeout; - - uint8_t buffer[32768]; -}; - -static int shout_init_count; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -shout_output_quark(void) -{ - return g_quark_from_static_string("shout_output"); -} - -static const struct encoder_plugin * -shout_encoder_plugin_get(const char *name) -{ - if (strcmp(name, "ogg") == 0) - name = "vorbis"; - else if (strcmp(name, "mp3") == 0) - name = "lame"; - - return encoder_plugin_get(name); -} - -static struct shout_data *new_shout_data(void) -{ - struct shout_data *ret = g_new(struct shout_data, 1); - - ret->shout_conn = shout_new(); - ret->shout_meta = shout_metadata_new(); - ret->bitrate = -1; - ret->quality = -2.0; - ret->timeout = DEFAULT_CONN_TIMEOUT; - - return ret; -} - -static void free_shout_data(struct shout_data *sd) -{ - if (sd->shout_meta) - shout_metadata_free(sd->shout_meta); - if (sd->shout_conn) - shout_free(sd->shout_conn); - - 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); \ - } \ - } - -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; - } - - const struct audio_format *audio_format = - &sd->base.config_audio_format; - if (!audio_format_fully_defined(audio_format)) { - g_set_error(error, shout_output_quark(), 0, - "Need full audio format specification"); - ao_base_finish(&sd->base); - free_shout_data(sd); - 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; - - 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; - } - - check_block_param("password"); - const char *passwd = block_param->value; - - check_block_param("name"); - const char *name = block_param->value; - - bool public = config_get_block_bool(param, "public", false); - - const char *user = config_get_block_string(param, "user", "source"); - - const char *value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { - char *test; - sd->quality = strtod(value, &test); - - if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) { - g_set_error(error, shout_output_quark(), 0, - "shout quality \"%s\" is not a number in the " - "range -1 to 10, line %i", - value, param->line); - goto failure; - } - - 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; - } - } 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; - } - - char *test; - sd->bitrate = strtol(value, &test, 10); - - if (*test != '\0' || sd->bitrate <= 0) { - g_set_error(error, shout_output_quark(), 0, - "bitrate must be a positive integer"); - goto failure; - } - } - - const char *encoding = config_get_block_string(param, "encoding", - "ogg"); - const struct encoder_plugin *encoder_plugin = - shout_encoder_plugin_get(encoding); - if (encoder_plugin == NULL) { - g_set_error(error, shout_output_quark(), 0, - "couldn't find shout encoder plugin \"%s\"", - encoding); - goto failure; - } - - sd->encoder = encoder_init(encoder_plugin, param, error); - if (sd->encoder == NULL) - goto failure; - - unsigned shout_format; - if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) - shout_format = SHOUT_FORMAT_MP3; - else - shout_format = SHOUT_FORMAT_OGG; - - unsigned protocol; - value = config_get_block_string(param, "protocol", NULL); - if (value != NULL) { - if (0 == strcmp(value, "shoutcast") && - 0 != strcmp(encoding, "mp3")) { - g_set_error(error, shout_output_quark(), 0, - "you cannot stream \"%s\" to shoutcast, use mp3", - encoding); - goto failure; - } else if (0 == strcmp(value, "shoutcast")) - protocol = SHOUT_PROTOCOL_ICY; - else if (0 == strcmp(value, "icecast1")) - protocol = SHOUT_PROTOCOL_XAUDIOCAST; - else if (0 == strcmp(value, "icecast2")) - protocol = SHOUT_PROTOCOL_HTTP; - else { - g_set_error(error, shout_output_quark(), 0, - "shout protocol \"%s\" is not \"shoutcast\" or " - "\"icecast1\"or \"icecast2\"", - value); - goto failure; - } - } else { - protocol = SHOUT_PROTOCOL_HTTP; - } - - if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS || - shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS || - shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS || - shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS || - shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS || - shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS || - shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS || - shout_set_format(sd->shout_conn, shout_format) - != SHOUTERR_SUCCESS || - shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS || - 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; - } - - /* optional paramters */ - sd->timeout = config_get_block_unsigned(param, "timeout", - DEFAULT_CONN_TIMEOUT); - - value = config_get_block_string(param, "genre", NULL); - 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; - } - - 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; - } - - value = config_get_block_string(param, "url", NULL); - if (value != NULL && shout_set_url(sd->shout_conn, value)) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - { - char temp[11]; - memset(temp, 0, sizeof(temp)); - - snprintf(temp, sizeof(temp), "%u", audio_format->channels); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp); - - snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate); - - shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp); - - if (sd->quality >= -1.0) { - snprintf(temp, sizeof(temp), "%2.2f", sd->quality); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY, - temp); - } else { - snprintf(temp, sizeof(temp), "%d", sd->bitrate); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE, - temp); - } - } - - return &sd->base; - -failure: - ao_base_finish(&sd->base); - free_shout_data(sd); - return NULL; -} - -static bool -handle_shout_error(struct shout_data *sd, int err, GError **error) -{ - switch (err) { - case SHOUTERR_SUCCESS: - break; - - case SHOUTERR_UNCONNECTED: - case SHOUTERR_SOCKET: - g_set_error(error, shout_output_quark(), err, - "Lost shout connection to %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - - default: - g_set_error(error, shout_output_quark(), err, - "connection to %s:%i error: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } - - return true; -} - -static bool -write_page(struct shout_data *sd, GError **error) -{ - assert(sd->encoder != NULL); - - while (true) { - size_t nbytes = encoder_read(sd->encoder, - sd->buffer, sizeof(sd->buffer)); - if (nbytes == 0) - return true; - - int err = shout_send(sd->shout_conn, sd->buffer, nbytes); - if (!handle_shout_error(sd, err, error)) - return false; - } - - return true; -} - -static void close_shout_conn(struct shout_data * sd) -{ - if (sd->encoder != NULL) { - if (encoder_end(sd->encoder, NULL)) - write_page(sd, NULL); - - encoder_close(sd->encoder); - } - - if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && - shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { - g_warning("problem closing connection to shout server: %s\n", - shout_get_error(sd->shout_conn)); - } -} - -static void -my_shout_finish_driver(struct audio_output *ao) -{ - struct shout_data *sd = (struct shout_data *)ao; - - encoder_finish(sd->encoder); - - ao_base_finish(&sd->base); - free_shout_data(sd); - - shout_init_count--; - - if (shout_init_count == 0) - shout_shutdown(); -} - -static void -my_shout_drop_buffered_audio(struct audio_output *ao) -{ - G_GNUC_UNUSED - struct shout_data *sd = (struct shout_data *)ao; - - /* needs to be implemented for shout */ -} - -static void -my_shout_close_device(struct audio_output *ao) -{ - struct shout_data *sd = (struct shout_data *)ao; - - close_shout_conn(sd); -} - -static bool -shout_connect(struct shout_data *sd, GError **error) -{ - switch (shout_open(sd->shout_conn)) { - case SHOUTERR_SUCCESS: - case SHOUTERR_CONNECTED: - return true; - - default: - g_set_error(error, shout_output_quark(), 0, - "problem opening connection to shout server %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } -} - -static bool -my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct shout_data *sd = (struct shout_data *)ao; - - if (!shout_connect(sd, error)) - return false; - - if (!encoder_open(sd->encoder, audio_format, error)) { - shout_close(sd->shout_conn); - return false; - } - - if (!write_page(sd, error)) { - encoder_close(sd->encoder); - shout_close(sd->shout_conn); - return false; - } - - return true; -} - -static unsigned -my_shout_delay(struct audio_output *ao) -{ - struct shout_data *sd = (struct shout_data *)ao; - - int delay = shout_delay(sd->shout_conn); - if (delay < 0) - delay = 0; - - return delay; -} - -static size_t -my_shout_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct shout_data *sd = (struct shout_data *)ao; - - return encoder_write(sd->encoder, chunk, size, error) && - write_page(sd, error) - ? size - : 0; -} - -static bool -my_shout_pause(struct audio_output *ao) -{ - static const char silence[1020]; - - return my_shout_play(ao, silence, sizeof(silence), NULL); -} - -static void -shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) -{ - char artist[size]; - char title[size]; - - artist[0] = 0; - title[0] = 0; - - for (unsigned i = 0; i < tag->num_items; i++) { - switch (tag->items[i]->type) { - case TAG_ARTIST: - strncpy(artist, tag->items[i]->value, size); - break; - case TAG_TITLE: - strncpy(title, tag->items[i]->value, size); - break; - - default: - break; - } - } - - snprintf(dest, size, "%s - %s", artist, title); -} - -static void my_shout_set_tag(struct audio_output *ao, - const struct tag *tag) -{ - struct shout_data *sd = (struct shout_data *)ao; - GError *error = NULL; - - if (sd->encoder->plugin->tag != NULL) { - /* encoder plugin supports stream tags */ - - if (!encoder_pre_tag(sd->encoder, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - if (!write_page(sd, NULL)) - return; - - if (!encoder_tag(sd->encoder, tag, &error)) { - g_warning("%s", error->message); - g_error_free(error); - } - } else { - /* no stream tag support: fall back to icy-metadata */ - char song[1024]; - shout_tag_to_metadata(tag, song, sizeof(song)); - - shout_metadata_add(sd->shout_meta, "song", song); - if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, - sd->shout_meta)) { - g_warning("error setting shout metadata\n"); - } - } - - write_page(sd, NULL); -} - -const struct audio_output_plugin shout_output_plugin = { - .name = "shout", - .init = my_shout_init_driver, - .finish = my_shout_finish_driver, - .open = my_shout_open_device, - .delay = my_shout_delay, - .play = my_shout_play, - .pause = my_shout_pause, - .cancel = my_shout_drop_buffered_audio, - .close = my_shout_close_device, - .send_tag = my_shout_set_tag, -}; |