diff options
Diffstat (limited to 'src/output/plugins')
41 files changed, 1314 insertions, 1357 deletions
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 28c374a00..22e13d7dc 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "AlsaOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "mixer/MixerList.hxx" #include "pcm/PcmExport.hxx" #include "config/ConfigError.hxx" @@ -131,17 +132,48 @@ struct AlsaOutput { mode(0), writei(snd_pcm_writei) { } + ~AlsaOutput() { + /* free libasound's config cache */ + snd_config_update_free_global(); + } + + gcc_pure + const char *GetDevice() { + return device.empty() ? default_device : device.c_str(); + } + bool Configure(const config_param ¶m, Error &error); + static AlsaOutput *Create(const config_param ¶m, Error &error); + + bool Enable(Error &error); + void Disable(); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + size_t Play(const void *chunk, size_t size, Error &error); + void Drain(); + void Cancel(); + +private: + bool SetupDop(AudioFormat audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + Error &error); + bool SetupOrDop(AudioFormat &audio_format, Error &error); + + int Recover(int err); + + /** + * Write silence to the ALSA device. + */ + void WriteSilence(snd_pcm_uframes_t nframes) { + writei(pcm, silence, nframes); + } + }; static constexpr Domain alsa_output_domain("alsa_output"); -static const char * -alsa_device(const AlsaOutput *ad) -{ - return ad->device.empty() ? default_device : ad->device.c_str(); -} - inline bool AlsaOutput::Configure(const config_param ¶m, Error &error) { @@ -178,8 +210,8 @@ AlsaOutput::Configure(const config_param ¶m, Error &error) return true; } -static AudioOutput * -alsa_init(const config_param ¶m, Error &error) +inline AlsaOutput * +AlsaOutput::Create(const config_param ¶m, Error &error) { AlsaOutput *ad = new AlsaOutput(); @@ -188,35 +220,20 @@ alsa_init(const config_param ¶m, Error &error) return nullptr; } - return &ad->base; + return ad; } -static void -alsa_finish(AudioOutput *ao) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - delete ad; - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -static bool -alsa_output_enable(AudioOutput *ao, gcc_unused Error &error) +inline bool +AlsaOutput::Enable(gcc_unused Error &error) { - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->pcm_export.Construct(); + pcm_export.Construct(); return true; } -static void -alsa_output_disable(AudioOutput *ao) +inline void +AlsaOutput::Disable() { - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->pcm_export.Destruct(); + pcm_export.Destruct(); } static bool @@ -450,7 +467,7 @@ configure_hw: if (err < 0) { FormatWarning(alsa_output_domain, "Cannot set mmap'ed mode on ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(-err)); + ad->GetDevice(), snd_strerror(-err)); LogWarning(alsa_output_domain, "Falling back to direct write mode"); ad->use_mmap = false; @@ -472,7 +489,7 @@ configure_hw: if (err < 0) { error.Format(alsa_output_domain, err, "ALSA device \"%s\" does not support format %s: %s", - alsa_device(ad), + ad->GetDevice(), sample_format_to_string(audio_format.format), snd_strerror(-err)); return false; @@ -489,7 +506,7 @@ configure_hw: if (err < 0) { error.Format(alsa_output_domain, err, "ALSA device \"%s\" does not support %i channels: %s", - alsa_device(ad), (int)audio_format.channels, + ad->GetDevice(), (int)audio_format.channels, snd_strerror(-err)); return false; } @@ -500,7 +517,7 @@ configure_hw: if (err < 0 || sample_rate == 0) { error.Format(alsa_output_domain, err, "ALSA device \"%s\" does not support %u Hz audio", - alsa_device(ad), audio_format.sample_rate); + ad->GetDevice(), audio_format.sample_rate); return false; } audio_format.sample_rate = sample_rate; @@ -631,16 +648,16 @@ configure_hw: error: error.Format(alsa_output_domain, err, "Error opening ALSA device \"%s\" (%s): %s", - alsa_device(ad), cmd, snd_strerror(-err)); + ad->GetDevice(), cmd, snd_strerror(-err)); return false; } -static bool -alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format, - bool *shift8_r, bool *packed_r, bool *reverse_endian_r, - Error &error) +inline bool +AlsaOutput::SetupDop(const AudioFormat audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + Error &error) { - assert(ad->dop); + assert(dop); assert(audio_format.format == SampleFormat::DSD); /* pass 24 bit to alsa_setup() */ @@ -651,7 +668,7 @@ alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format, const AudioFormat check = dop_format; - if (!alsa_setup(ad, dop_format, packed_r, reverse_endian_r, error)) + if (!alsa_setup(this, dop_format, packed_r, reverse_endian_r, error)) return false; /* if the device allows only 32 bit, shift all DoP @@ -668,102 +685,91 @@ alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format, for DSD over USB */ error.Format(alsa_output_domain, "Failed to configure DSD-over-PCM on ALSA device \"%s\"", - alsa_device(ad)); - delete[] ad->silence; + GetDevice()); + delete[] silence; return false; } return true; } -static bool -alsa_setup_or_dop(AlsaOutput *ad, AudioFormat &audio_format, - Error &error) +inline bool +AlsaOutput::SetupOrDop(AudioFormat &audio_format, Error &error) { bool shift8 = false, packed, reverse_endian; - const bool dop = ad->dop && + const bool dop2 = dop && audio_format.format == SampleFormat::DSD; - const bool success = dop - ? alsa_setup_dop(ad, audio_format, - &shift8, &packed, &reverse_endian, - error) - : alsa_setup(ad, audio_format, &packed, &reverse_endian, + const bool success = dop2 + ? SetupDop(audio_format, + &shift8, &packed, &reverse_endian, + error) + : alsa_setup(this, audio_format, &packed, &reverse_endian, error); if (!success) return false; - ad->pcm_export->Open(audio_format.format, - audio_format.channels, - dop, shift8, packed, reverse_endian); + pcm_export->Open(audio_format.format, + audio_format.channels, + dop2, shift8, packed, reverse_endian); return true; } -static bool -alsa_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) +inline bool +AlsaOutput::Open(AudioFormat &audio_format, Error &error) { - AlsaOutput *ad = (AlsaOutput *)ao; - - int err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); + int err = snd_pcm_open(&pcm, GetDevice(), + SND_PCM_STREAM_PLAYBACK, mode); if (err < 0) { error.Format(alsa_output_domain, err, "Failed to open ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(err)); + GetDevice(), snd_strerror(err)); return false; } FormatDebug(alsa_output_domain, "opened %s type=%s", - snd_pcm_name(ad->pcm), - snd_pcm_type_name(snd_pcm_type(ad->pcm))); + snd_pcm_name(pcm), + snd_pcm_type_name(snd_pcm_type(pcm))); - if (!alsa_setup_or_dop(ad, audio_format, error)) { - snd_pcm_close(ad->pcm); + if (!SetupOrDop(audio_format, error)) { + snd_pcm_close(pcm); return false; } - ad->in_frame_size = audio_format.GetFrameSize(); - ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format); + in_frame_size = audio_format.GetFrameSize(); + out_frame_size = pcm_export->GetFrameSize(audio_format); - ad->must_prepare = false; + must_prepare = false; 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(AlsaOutput *ad, int err) +inline int +AlsaOutput::Recover(int err) { if (err == -EPIPE) { FormatDebug(alsa_output_domain, - "Underrun on ALSA device \"%s\"", alsa_device(ad)); + "Underrun on ALSA device \"%s\"", + GetDevice()); } else if (err == -ESTRPIPE) { FormatDebug(alsa_output_domain, "ALSA device \"%s\" was suspended", - alsa_device(ad)); + GetDevice()); } - switch (snd_pcm_state(ad->pcm)) { + switch (snd_pcm_state(pcm)) { case SND_PCM_STATE_PAUSED: - err = snd_pcm_pause(ad->pcm, /* disable */ 0); + err = snd_pcm_pause(pcm, /* disable */ 0); break; case SND_PCM_STATE_SUSPENDED: - err = snd_pcm_resume(ad->pcm); + err = snd_pcm_resume(pcm); if (err == -EAGAIN) return 0; /* fall-through to snd_pcm_prepare: */ case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: - ad->period_position = 0; - err = snd_pcm_prepare(ad->pcm); + period_position = 0; + err = snd_pcm_prepare(pcm); break; case SND_PCM_STATE_DISCONNECTED: break; @@ -779,67 +785,58 @@ alsa_recover(AlsaOutput *ad, int err) return err; } -static void -alsa_drain(AudioOutput *ao) +inline void +AlsaOutput::Drain() { - AlsaOutput *ad = (AlsaOutput *)ao; - - if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + if (snd_pcm_state(pcm) != SND_PCM_STATE_RUNNING) return; - if (ad->period_position > 0) { + if (period_position > 0) { /* generate some silence to finish the partial period */ snd_pcm_uframes_t nframes = - ad->period_frames - ad->period_position; - alsa_write_silence(ad, nframes); + period_frames - period_position; + WriteSilence(nframes); } - snd_pcm_drain(ad->pcm); + snd_pcm_drain(pcm); - ad->period_position = 0; + period_position = 0; } -static void -alsa_cancel(AudioOutput *ao) +inline void +AlsaOutput::Cancel() { - AlsaOutput *ad = (AlsaOutput *)ao; + period_position = 0; + must_prepare = true; - ad->period_position = 0; - ad->must_prepare = true; - - snd_pcm_drop(ad->pcm); + snd_pcm_drop(pcm); } -static void -alsa_close(AudioOutput *ao) +inline void +AlsaOutput::Close() { - AlsaOutput *ad = (AlsaOutput *)ao; - - snd_pcm_close(ad->pcm); - delete[] ad->silence; + snd_pcm_close(pcm); + delete[] silence; } -static size_t -alsa_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +AlsaOutput::Play(const void *chunk, size_t size, Error &error) { - AlsaOutput *ad = (AlsaOutput *)ao; - assert(size > 0); - assert(size % ad->in_frame_size == 0); + assert(size % in_frame_size == 0); - if (ad->must_prepare) { - ad->must_prepare = false; + if (must_prepare) { + must_prepare = false; - int err = snd_pcm_prepare(ad->pcm); + int err = snd_pcm_prepare(pcm); if (err < 0) { error.Set(alsa_output_domain, err, snd_strerror(-err)); return 0; } } - const auto e = ad->pcm_export->Export({chunk, size}); + const auto e = pcm_export->Export({chunk, size}); if (e.size == 0) /* the DoP (DSD over PCM) filter converts two frames at a time and ignores the last odd frame; if there @@ -852,43 +849,45 @@ alsa_play(AudioOutput *ao, const void *chunk, size_t size, chunk = e.data; size = e.size; - assert(size % ad->out_frame_size == 0); + assert(size % out_frame_size == 0); - size /= ad->out_frame_size; + size /= out_frame_size; assert(size > 0); while (true) { - snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); + snd_pcm_sframes_t ret = writei(pcm, chunk, size); if (ret > 0) { - ad->period_position = (ad->period_position + ret) - % ad->period_frames; + period_position = (period_position + ret) + % period_frames; - size_t bytes_written = ret * ad->out_frame_size; - return ad->pcm_export->CalcSourceSize(bytes_written); + size_t bytes_written = ret * out_frame_size; + return pcm_export->CalcSourceSize(bytes_written); } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && - alsa_recover(ad, ret) < 0) { + Recover(ret) < 0) { error.Set(alsa_output_domain, ret, snd_strerror(-ret)); return 0; } } } +typedef AudioOutputWrapper<AlsaOutput> Wrapper; + const struct AudioOutputPlugin alsa_output_plugin = { "alsa", alsa_test_default_device, - alsa_init, - alsa_finish, - alsa_output_enable, - alsa_output_disable, - alsa_open, - alsa_close, + &Wrapper::Init, + &Wrapper::Finish, + &Wrapper::Enable, + &Wrapper::Disable, + &Wrapper::Open, + &Wrapper::Close, nullptr, nullptr, - alsa_play, - alsa_drain, - alsa_cancel, + &Wrapper::Play, + &Wrapper::Drain, + &Wrapper::Cancel, nullptr, &alsa_mixer_plugin, diff --git a/src/output/plugins/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx index f72116f91..ff7d439a9 100644 --- a/src/output/plugins/AlsaOutputPlugin.hxx +++ b/src/output/plugins/AlsaOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx index af8c88fa1..31edd371e 100644 --- a/src/output/plugins/AoOutputPlugin.cxx +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,12 +20,13 @@ #include "config.h" #include "AoOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "util/DivideString.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include <ao/ao.h> -#include <glib.h> #include <string.h> @@ -126,25 +127,18 @@ AoOutput::Configure(const config_param ¶m, Error &error) value = param.GetBlockValue("options", nullptr); if (value != nullptr) { - gchar **_options = g_strsplit(value, ";", 0); + for (const auto &i : SplitString(value, ';')) { + const DivideString ss(i.c_str(), '=', true); - for (unsigned i = 0; _options[i] != nullptr; ++i) { - gchar **key_value = g_strsplit(_options[i], "=", 2); - - if (key_value[0] == nullptr || key_value[1] == nullptr) { + if (!ss.IsDefined()) { error.Format(ao_output_domain, "problems parsing options \"%s\"", - _options[i]); + i.c_str()); return false; } - ao_append_option(&options, key_value[0], - key_value[1]); - - g_strfreev(key_value); + ao_append_option(&options, ss.GetFirst(), ss.GetSecond()); } - - g_strfreev(_options); } return true; diff --git a/src/output/plugins/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx index 07c2ba16b..582070c47 100644 --- a/src/output/plugins/AoOutputPlugin.hxx +++ b/src/output/plugins/AoOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx index 9df5a74dd..40ae6a370 100644 --- a/src/output/plugins/FifoOutputPlugin.cxx +++ b/src/output/plugins/FifoOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -260,14 +260,13 @@ fifo_output_play(AudioOutput *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); + ssize_t bytes = write(fd->output, chunk, size); if (bytes > 0) return (size_t)bytes; diff --git a/src/output/plugins/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx index f41ceded6..353be51a6 100644 --- a/src/output/plugins/FifoOutputPlugin.hxx +++ b/src/output/plugins/FifoOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index e1dad7893..5c136b81f 100644 --- a/src/output/plugins/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,26 +20,27 @@ #include "config.h" #include "JackOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "config/ConfigError.hxx" +#include "util/ConstBuffer.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include <assert.h> -#include <glib.h> #include <jack/jack.h> #include <jack/types.h> #include <jack/ringbuffer.h> +#include <unistd.h> /* for usleep() */ #include <stdlib.h> #include <string.h> -enum { - MAX_PORTS = 16, -}; +static constexpr unsigned MAX_PORTS = 16; -static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); +static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t); struct JackOutput { AudioOutput base; @@ -55,10 +56,10 @@ struct JackOutput { /* configuration */ - char *source_ports[MAX_PORTS]; + std::string source_ports[MAX_PORTS]; unsigned num_source_ports; - char *destination_ports[MAX_PORTS]; + std::string destination_ports[MAX_PORTS]; unsigned num_destination_ports; size_t ringbuffer_size; @@ -82,24 +83,65 @@ struct JackOutput { JackOutput() :base(jack_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + bool Configure(const config_param ¶m, Error &error); + + bool Connect(Error &error); + + /** + * Disconnect the JACK client. + */ + void Disconnect(); + + void Shutdown() { + shutdown = true; + } + + bool Enable(Error &error); + void Disable(); + + bool Open(AudioFormat &new_audio_format, Error &error); + + void Close() { + Stop(); + } + + bool Start(Error &error); + void Stop(); + + /** + * Determine the number of frames guaranteed to be available + * on all channels. + */ + gcc_pure + jack_nframes_t GetAvailable() const; + + void Process(jack_nframes_t nframes); + + /** + * @return the number of frames that were written + */ + size_t WriteSamples(const float *src, size_t n_frames); + + unsigned Delay() const { + return base.pause && pause && !shutdown + ? 1000 + : 0; } + + size_t Play(const void *chunk, size_t size, Error &error); + + bool Pause(); }; 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 JackOutput *jd) +inline jack_nframes_t +JackOutput::GetAvailable() const { - size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); + size_t min = jack_ringbuffer_read_space(ringbuffer[0]); - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); + for (unsigned i = 1; i < audio_format.channels; ++i) { + size_t current = jack_ringbuffer_read_space(ringbuffer[i]); if (current < min) min = current; } @@ -109,85 +151,121 @@ mpd_jack_available(const JackOutput *jd) return min / jack_sample_size; } -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) +/** + * Call jack_ringbuffer_read_advance() on all buffers in the list. + */ +static void +MultiReadAdvance(ConstBuffer<jack_ringbuffer_t *> buffers, + size_t size) +{ + for (auto *i : buffers) + jack_ringbuffer_read_advance(i, size); +} + +/** + * Write a specific amount of "silence" to the given port. + */ +static void +WriteSilence(jack_port_t &port, jack_nframes_t nframes) +{ + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(&port, nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is invoked + anyway, but unable to get a buffer */ + return; + + std::fill_n(out, nframes, 0.0); +} + +/** + * Write a specific amount of "silence" to all ports in the list. + */ +static void +MultiWriteSilence(ConstBuffer<jack_port_t *> ports, jack_nframes_t nframes) +{ + for (auto *i : ports) + WriteSilence(*i, nframes); +} + +/** + * Copy data from the buffer to the port. If the buffer underruns, + * fill with silence. + */ +static void +Copy(jack_port_t &dest, jack_nframes_t nframes, + jack_ringbuffer_t &src, jack_nframes_t available) { - JackOutput *jd = (JackOutput *) arg; + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(&dest, nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + return; + + /* copy from buffer to port */ + jack_ringbuffer_read(&src, (char *)out, + available * jack_sample_size); + /* ringbuffer underrun, fill with silence */ + std::fill(out + available, out + nframes, 0.0); +} + +inline void +JackOutput::Process(jack_nframes_t nframes) +{ if (nframes <= 0) - return 0; + return; - if (jd->pause) { + jack_nframes_t available = GetAvailable(); + + const unsigned n_channels = audio_format.channels; + + if (pause) { /* empty the ring buffers */ - const jack_nframes_t available = mpd_jack_available(jd); - for (unsigned i = 0; i < jd->audio_format.channels; ++i) - jack_ringbuffer_read_advance(jd->ringbuffer[i], - available * jack_sample_size); + MultiReadAdvance({ringbuffer, n_channels}, + available * jack_sample_size); /* generate silence while MPD is paused */ - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - 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; - } + MultiWriteSilence({ports, n_channels}, nframes); - return 0; + return; } - jack_nframes_t available = mpd_jack_available(jd); if (available > nframes) available = nframes; - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - 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 - buffer */ - continue; - - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * jack_sample_size); - - for (jack_nframes_t f = available; f < nframes; ++f) - /* ringbuffer underrun, fill with silence */ - out[f] = 0.0; - } + for (unsigned i = 0; i < n_channels; ++i) + Copy(*ports[i], nframes, *ringbuffer[i], available); /* generate silence for the unused source ports */ - for (unsigned i = jd->audio_format.channels; - i < jd->num_source_ports; ++i) { - 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 - buffer */ - continue; - - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } + MultiWriteSilence({ports + n_channels, num_source_ports - n_channels}, + nframes); +} +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + JackOutput &jo = *(JackOutput *) arg; + + jo.Process(nframes); return 0; } static void mpd_jack_shutdown(void *arg) { - JackOutput *jd = (JackOutput *) arg; - jd->shutdown = true; + JackOutput &jo = *(JackOutput *) arg; + + jo.Shutdown(); } static void @@ -200,9 +278,10 @@ set_audioformat(JackOutput *jd, AudioFormat &audio_format) else if (audio_format.channels > jd->num_source_ports) audio_format.channels = 2; - if (audio_format.format != SampleFormat::S16 && - audio_format.format != SampleFormat::S24_P32) - audio_format.format = SampleFormat::S24_P32; + /* JACK uses 32 bit float in the range [-1 .. 1] - just like + MPD's SampleFormat::FLOAT*/ + static_assert(jack_sample_size == sizeof(float), "Expected float32"); + audio_format.format = SampleFormat::FLOAT; } static void @@ -219,55 +298,47 @@ mpd_jack_info(const char *msg) } #endif -/** - * Disconnect the JACK client. - */ -static void -mpd_jack_disconnect(JackOutput *jd) +void +JackOutput::Disconnect() { - assert(jd != nullptr); - assert(jd->client != nullptr); + assert(client != nullptr); - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = nullptr; + jack_deactivate(client); + jack_client_close(client); + client = nullptr; } /** * Connect the JACK client and performs some basic setup * (e.g. register callbacks). */ -static bool -mpd_jack_connect(JackOutput *jd, Error &error) +bool +JackOutput::Connect(Error &error) { - jack_status_t status; - - assert(jd != nullptr); + shutdown = false; - jd->shutdown = false; - - jd->client = jack_client_open(jd->name, jd->options, &status, - jd->server_name); - if (jd->client == nullptr) { + jack_status_t status; + client = jack_client_open(name, options, &status, server_name); + if (client == nullptr) { error.Format(jack_output_domain, status, "Failed to connect to JACK server, status=%d", status); return false; } - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + jack_set_process_callback(client, mpd_jack_process, this); + jack_on_shutdown(client, mpd_jack_shutdown, this); - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - jd->ports[i] = jack_port_register(jd->client, - jd->source_ports[i], - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - if (jd->ports[i] == nullptr) { + for (unsigned i = 0; i < num_source_ports; ++i) { + ports[i] = jack_port_register(client, + source_ports[i].c_str(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (ports[i] == nullptr) { error.Format(jack_output_domain, "Cannot register output port \"%s\"", - jd->source_ports[i]); - mpd_jack_disconnect(jd); + source_ports[i].c_str()); + Disconnect(); return false; } } @@ -282,23 +353,19 @@ mpd_jack_test_default_device(void) } static unsigned -parse_port_list(const char *source, char **dest, Error &error) +parse_port_list(const char *source, std::string dest[], Error &error) { - char **list = g_strsplit(source, ",", 0); unsigned n = 0; - - for (n = 0; list[n] != nullptr; ++n) { + for (auto &&i : SplitString(source, ',')) { if (n >= MAX_PORTS) { error.Set(config_domain, "too many port names"); return 0; } - dest[n] = list[n]; + dest[n++] = std::move(i); } - g_free(list); - if (n == 0) { error.Format(config_domain, "at least one port name expected"); @@ -308,42 +375,35 @@ parse_port_list(const char *source, char **dest, Error &error) return n; } -static AudioOutput * -mpd_jack_init(const config_param ¶m, Error &error) +bool +JackOutput::Configure(const config_param ¶m, Error &error) { - JackOutput *jd = new JackOutput(); - - if (!jd->Initialize(param, error)) { - delete jd; - return nullptr; - } - - const char *value; + if (!base.Configure(param, error)) + return false; - jd->options = JackNullOption; + options = JackNullOption; - jd->name = param.GetBlockValue("client_name", nullptr); - if (jd->name != nullptr) - jd->options = jack_options_t(jd->options | JackUseExactName); + name = param.GetBlockValue("client_name", nullptr); + if (name != nullptr) + options = jack_options_t(options | JackUseExactName); else /* if there's a no configured client name, we don't care about the JackUseExactName option */ - jd->name = "Music Player Daemon"; + name = "Music Player Daemon"; - jd->server_name = param.GetBlockValue("server_name", nullptr); - if (jd->server_name != nullptr) - jd->options = jack_options_t(jd->options | JackServerName); + server_name = param.GetBlockValue("server_name", nullptr); + if (server_name != nullptr) + options = jack_options_t(options | JackServerName); if (!param.GetBlockValue("autostart", false)) - jd->options = jack_options_t(jd->options | JackNoStartServer); + options = jack_options_t(options | JackNoStartServer); /* configure the source ports */ - 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 nullptr; + const char *value = param.GetBlockValue("source_ports", "left,right"); + num_source_ports = parse_port_list(value, source_ports, error); + if (num_source_ports == 0) + return false; /* configure the destination ports */ @@ -358,193 +418,178 @@ mpd_jack_init(const config_param ¶m, Error &error) } if (value != nullptr) { - jd->num_destination_ports = - parse_port_list(value, - jd->destination_ports, error); - if (jd->num_destination_ports == 0) - return nullptr; + num_destination_ports = + parse_port_list(value, destination_ports, error); + if (num_destination_ports == 0) + return false; } else { - jd->num_destination_ports = 0; + num_destination_ports = 0; } - if (jd->num_destination_ports > 0 && - jd->num_destination_ports != jd->num_source_ports) + if (num_destination_ports > 0 && + num_destination_ports != num_source_ports) 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, + num_source_ports, num_destination_ports, param.line); - jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); - - jack_set_error_function(mpd_jack_error); + ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); -#ifdef HAVE_JACK_SET_INFO_FUNCTION - jack_set_info_function(mpd_jack_info); -#endif - - return &jd->base; + return true; } -static void -mpd_jack_finish(AudioOutput *ao) +inline bool +JackOutput::Enable(Error &error) { - JackOutput *jd = (JackOutput *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - g_free(jd->source_ports[i]); + for (unsigned i = 0; i < num_source_ports; ++i) + ringbuffer[i] = nullptr; - for (unsigned i = 0; i < jd->num_destination_ports; ++i) - g_free(jd->destination_ports[i]); - - delete jd; + return Connect(error); } -static bool -mpd_jack_enable(AudioOutput *ao, Error &error) +inline void +JackOutput::Disable() { - JackOutput *jd = (JackOutput *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - jd->ringbuffer[i] = nullptr; + if (client != nullptr) + Disconnect(); - return mpd_jack_connect(jd, error); + for (unsigned i = 0; i < num_source_ports; ++i) { + if (ringbuffer[i] != nullptr) { + jack_ringbuffer_free(ringbuffer[i]); + ringbuffer[i] = nullptr; + } + } } -static void -mpd_jack_disable(AudioOutput *ao) +static AudioOutput * +mpd_jack_init(const config_param ¶m, Error &error) { - JackOutput *jd = (JackOutput *)ao; - - if (jd->client != nullptr) - mpd_jack_disconnect(jd); + JackOutput *jd = new JackOutput(); - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] != nullptr) { - jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = nullptr; - } + if (!jd->Configure(param, error)) { + delete jd; + return nullptr; } + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return &jd->base; } /** * Stops the playback on the JACK connection. */ -static void -mpd_jack_stop(JackOutput *jd) +void +JackOutput::Stop() { - assert(jd != nullptr); - - if (jd->client == nullptr) + if (client == nullptr) return; - if (jd->shutdown) + if (shutdown) /* the connection has failed; close it */ - mpd_jack_disconnect(jd); + Disconnect(); else /* the connection is alive: just stop playback */ - jack_deactivate(jd->client); + jack_deactivate(client); } -static bool -mpd_jack_start(JackOutput *jd, Error &error) +inline bool +JackOutput::Start(Error &error) { - const char *destination_ports[MAX_PORTS], **jports; - const char *duplicate_port = nullptr; - unsigned num_destination_ports; - - assert(jd->client != nullptr); - assert(jd->audio_format.channels <= jd->num_source_ports); + assert(client != nullptr); + assert(audio_format.channels <= num_source_ports); /* allocate the ring buffers on the first open(); these persist until MPD exits. It's too unsafe to delete them 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] == nullptr) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); + for (unsigned i = 0; i < num_source_ports; ++i) { + if (ringbuffer[i] == nullptr) + ringbuffer[i] = + jack_ringbuffer_create(ringbuffer_size); /* clear the ring buffer to be sure that data from previous playbacks are gone */ - jack_ringbuffer_reset(jd->ringbuffer[i]); + jack_ringbuffer_reset(ringbuffer[i]); } - if ( jack_activate(jd->client) ) { + if ( jack_activate(client) ) { error.Set(jack_output_domain, "cannot activate client"); - mpd_jack_stop(jd); + Stop(); return false; } - if (jd->num_destination_ports == 0) { + const char *dports[MAX_PORTS], **jports; + unsigned num_dports; + if (num_destination_ports == 0) { /* no output ports were configured - ask libjack for defaults */ - jports = jack_get_ports(jd->client, nullptr, nullptr, + jports = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); if (jports == nullptr) { error.Set(jack_output_domain, "no ports found"); - mpd_jack_stop(jd); + Stop(); return false; } assert(*jports != nullptr); - for (num_destination_ports = 0; - num_destination_ports < MAX_PORTS && - jports[num_destination_ports] != nullptr; - ++num_destination_ports) { + for (num_dports = 0; num_dports < MAX_PORTS && + jports[num_dports] != nullptr; + ++num_dports) { 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]; + num_dports, + jports[num_dports]); + dports[num_dports] = jports[num_dports]; } } else { /* use the configured output ports */ - num_destination_ports = jd->num_destination_ports; - memcpy(destination_ports, jd->destination_ports, - num_destination_ports * sizeof(*destination_ports)); + num_dports = num_destination_ports; + for (unsigned i = 0; i < num_dports; ++i) + dports[i] = destination_ports[i].c_str(); jports = nullptr; } - assert(num_destination_ports > 0); + assert(num_dports > 0); - if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + const char *duplicate_port = nullptr; + if (audio_format.channels >= 2 && num_dports == 1) { /* mix stereo signal on one speaker */ - while (num_destination_ports < jd->audio_format.channels) - destination_ports[num_destination_ports++] = - destination_ports[0]; - } else if (num_destination_ports > jd->audio_format.channels) { - if (jd->audio_format.channels == 1 && num_destination_ports > 2) { + std::fill(dports + num_dports, dports + audio_format.channels, + dports[0]); + } else if (num_dports > audio_format.channels) { + if (audio_format.channels == 1 && num_dports > 2) { /* mono input file: connect the one source channel to the both destination channels */ - duplicate_port = destination_ports[1]; - num_destination_ports = 1; + duplicate_port = dports[1]; + num_dports = 1; } else /* connect only as many ports as we need */ - num_destination_ports = jd->audio_format.channels; + num_dports = audio_format.channels; } - assert(num_destination_ports <= jd->num_source_ports); + assert(num_dports <= num_source_ports); - for (unsigned i = 0; i < num_destination_ports; ++i) { - int ret; - - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - destination_ports[i]); + for (unsigned i = 0; i < num_dports; ++i) { + int ret = jack_connect(client, jack_port_name(ports[i]), + dports[i]); if (ret != 0) { error.Format(jack_output_domain, - "Not a valid JACK port: %s", - destination_ports[i]); + "Not a valid JACK port: %s", dports[i]); if (jports != nullptr) free(jports); - mpd_jack_stop(jd); + Stop(); return false; } } @@ -554,7 +599,7 @@ mpd_jack_start(JackOutput *jd, Error &error) the both destination channels */ int ret; - ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), + ret = jack_connect(client, jack_port_name(ports[0]), duplicate_port); if (ret != 0) { error.Format(jack_output_domain, @@ -564,7 +609,7 @@ mpd_jack_start(JackOutput *jd, Error &error) if (jports != nullptr) free(jports); - mpd_jack_stop(jd); + Stop(); return false; } } @@ -575,188 +620,119 @@ mpd_jack_start(JackOutput *jd, Error &error) return true; } -static bool -mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +JackOutput::Open(AudioFormat &new_audio_format, Error &error) { - JackOutput *jd = (JackOutput *)ao; + pause = false; - assert(jd != nullptr); + if (client != nullptr && shutdown) + Disconnect(); - jd->pause = false; - - if (jd->client != nullptr && jd->shutdown) - mpd_jack_disconnect(jd); - - if (jd->client == nullptr && !mpd_jack_connect(jd, error)) + if (client == nullptr && !Connect(error)) return false; - set_audioformat(jd, audio_format); - jd->audio_format = audio_format; + set_audioformat(this, new_audio_format); + audio_format = new_audio_format; - if (!mpd_jack_start(jd, error)) - return false; - - return true; + return Start(error); } -static void -mpd_jack_close(gcc_unused AudioOutput *ao) +inline size_t +JackOutput::WriteSamples(const float *src, size_t n_frames) { - JackOutput *jd = (JackOutput *)ao; + assert(n_frames > 0); - mpd_jack_stop(jd); -} + const unsigned n_channels = audio_format.channels; -static unsigned -mpd_jack_delay(AudioOutput *ao) -{ - JackOutput *jd = (JackOutput *)ao; + float *dest[MAX_CHANNELS]; + size_t space = -1; + for (unsigned i = 0; i < n_channels; ++i) { + jack_ringbuffer_data_t d[2]; + jack_ringbuffer_get_write_vector(ringbuffer[i], d); - return jd->base.pause && jd->pause && !jd->shutdown - ? 1000 - : 0; -} + /* choose the first non-empty writable area */ + const jack_ringbuffer_data_t &e = d[d[0].len == 0]; -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} + if (e.len < space) + /* send data symmetrically */ + space = e.len; -static void -mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - 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], - (const char *)&sample, - sizeof(sample)); - } + dest[i] = (float *)e.buf; } -} -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} + space /= jack_sample_size; + if (space == 0) + return 0; -static void -mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - 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], - (const char *)&sample, - sizeof(sample)); - } - } -} + const size_t result = n_frames = std::min(space, n_frames); -static void -mpd_jack_write_samples(JackOutput *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.format) { - case SampleFormat::S16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case SampleFormat::S24_P32: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - gcc_unreachable(); - } + while (n_frames-- > 0) + for (unsigned i = 0; i < n_channels; ++i) + *dest[i]++ = *src++; + + const size_t per_channel_advance = result * jack_sample_size; + for (unsigned i = 0; i < n_channels; ++i) + jack_ringbuffer_write_advance(ringbuffer[i], + per_channel_advance); + + return result; } -static size_t -mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +JackOutput::Play(const void *chunk, size_t size, Error &error) { - JackOutput *jd = (JackOutput *)ao; - const size_t frame_size = jd->audio_format.GetFrameSize(); - size_t space = 0, space1; - - jd->pause = false; + pause = false; + const size_t frame_size = audio_format.GetFrameSize(); assert(size % frame_size == 0); size /= frame_size; while (true) { - if (jd->shutdown) { + if (shutdown) { error.Set(jack_output_domain, "Refusing to play, because " "there is no client thread"); return 0; } - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); - if (space > space1) - /* send data symmetrically */ - space = space1; - } - - if (space >= jack_sample_size) - break; + size_t frames_written = + WriteSamples((const float *)chunk, size); + if (frames_written > 0) + return frames_written * frame_size; /* XXX do something more intelligent to synchronize */ - g_usleep(1000); + usleep(1000); } - - space /= jack_sample_size; - if (space < size) - size = space; - - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; } -static bool -mpd_jack_pause(AudioOutput *ao) +inline bool +JackOutput::Pause() { - JackOutput *jd = (JackOutput *)ao; - - if (jd->shutdown) + if (shutdown) return false; - jd->pause = true; + pause = true; return true; } +typedef AudioOutputWrapper<JackOutput> Wrapper; + const struct AudioOutputPlugin jack_output_plugin = { "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, + &Wrapper::Finish, + &Wrapper::Enable, + &Wrapper::Disable, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - mpd_jack_play, + &Wrapper::Play, nullptr, nullptr, - mpd_jack_pause, + &Wrapper::Pause, nullptr, }; diff --git a/src/output/plugins/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx index 6f1f7ecb9..f76431690 100644 --- a/src/output/plugins/JackOutputPlugin.hxx +++ b/src/output/plugins/JackOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx index 098f58926..eb6a184ef 100644 --- a/src/output/plugins/NullOutputPlugin.cxx +++ b/src/output/plugins/NullOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx index f25f5b9f3..9a1d1558b 100644 --- a/src/output/plugins/NullOutputPlugin.hxx +++ b/src/output/plugins/NullOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx index 13ac7b35e..3274d5b45 100644 --- a/src/output/plugins/OSXOutputPlugin.cxx +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -65,13 +65,13 @@ osx_output_configure(OSXOutput *oo, const config_param ¶m) { const char *device = param.GetBlockValue("device"); - if (device == NULL || 0 == strcmp(device, "default")) { + if (device == nullptr || 0 == strcmp(device, "default")) { oo->component_subtype = kAudioUnitSubType_DefaultOutput; - oo->device_name = NULL; + oo->device_name = nullptr; } else if (0 == strcmp(device, "system")) { oo->component_subtype = kAudioUnitSubType_SystemOutput; - oo->device_name = NULL; + oo->device_name = nullptr; } else { oo->component_subtype = kAudioUnitSubType_HALOutput; @@ -86,7 +86,7 @@ osx_output_init(const config_param ¶m, Error &error) OSXOutput *oo = new OSXOutput(); if (!oo->base.Configure(param, error)) { delete oo; - return NULL; + return nullptr; } osx_output_configure(oo, param); @@ -108,7 +108,7 @@ osx_output_set_device(OSXOutput *oo, Error &error) bool ret = true; OSStatus status; UInt32 size, numdevices; - AudioDeviceID *deviceids = NULL; + AudioDeviceID *deviceids = nullptr; char name[256]; unsigned int i; @@ -118,7 +118,7 @@ osx_output_set_device(OSXOutput *oo, Error &error) /* how many audio devices are there? */ status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, - NULL); + nullptr); if (status != noErr) { error.Format(osx_output_domain, status, "Unable to determine number of OS X audio devices: %s", @@ -206,7 +206,7 @@ osx_render(void *vdata, AudioBuffer *buffer = &buffer_list->mBuffers[0]; size_t buffer_size = buffer->mDataByteSize; - assert(od->buffer != NULL); + assert(od->buffer != nullptr); od->mutex.lock(); @@ -245,7 +245,7 @@ osx_output_enable(AudioOutput *ao, Error &error) desc.componentFlags = 0; desc.componentFlagsMask = 0; - Component comp = FindNextComponent(NULL, &desc); + Component comp = FindNextComponent(nullptr, &desc); if (comp == 0) { error.Set(osx_output_domain, "Error finding OS X component"); diff --git a/src/output/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx index d7aed40b6..89cd3fe91 100644 --- a/src/output/plugins/OSXOutputPlugin.hxx +++ b/src/output/plugins/OSXOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx index 2f095c0a4..c28250eb0 100644 --- a/src/output/plugins/OpenALOutputPlugin.cxx +++ b/src/output/plugins/OpenALOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "OpenALOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -33,10 +34,12 @@ #include <OpenAL/alc.h> #endif -/* should be enough for buffer size = 2048 */ -#define NUM_BUFFERS 16 +class OpenALOutput { + friend struct AudioOutputWrapper<OpenALOutput>; + + /* should be enough for buffer size = 2048 */ + static constexpr unsigned NUM_BUFFERS = 16; -struct OpenALOutput { AudioOutput base; const char *device_name; @@ -51,9 +54,47 @@ struct OpenALOutput { OpenALOutput() :base(openal_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + bool Configure(const config_param ¶m, Error &error); + + static OpenALOutput *Create(const config_param ¶m, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + + void Close(); + + gcc_pure + unsigned Delay() const { + return filled < NUM_BUFFERS || HasProcessed() + ? 0 + /* we don't know exactly how long we must wait + for the next buffer to finish, so this is a + random guess: */ + : 50; + } + + size_t Play(const void *chunk, size_t size, Error &error); + + void Cancel(); + +private: + gcc_pure + ALint GetSourceI(ALenum param) const { + ALint value; + alGetSourcei(source, param, &value); + return value; + } + + gcc_pure + bool HasProcessed() const { + return GetSourceI(AL_BUFFERS_PROCESSED) > 0; } + + gcc_pure + bool IsPlaying() const { + return GetSourceI(AL_SOURCE_STATE) == AL_PLAYING; + } + + bool SetupContext(Error &error); }; static constexpr Domain openal_output_domain("openal_output"); @@ -83,200 +124,154 @@ openal_audio_format(AudioFormat &audio_format) } } -gcc_pure -static inline ALint -openal_get_source_i(const OpenALOutput *od, ALenum param) -{ - ALint value; - alGetSourcei(od->source, param, &value); - return value; -} - -gcc_pure -static inline bool -openal_has_processed(const OpenALOutput *od) -{ - return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; -} - -gcc_pure -static inline ALint -openal_is_playing(const OpenALOutput *od) -{ - return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; -} - -static bool -openal_setup_context(OpenALOutput *od, Error &error) +inline bool +OpenALOutput::SetupContext(Error &error) { - od->device = alcOpenDevice(od->device_name); + device = alcOpenDevice(device_name); - if (od->device == nullptr) { + if (device == nullptr) { error.Format(openal_output_domain, "Error opening OpenAL device \"%s\"", - od->device_name); + device_name); return false; } - od->context = alcCreateContext(od->device, nullptr); + context = alcCreateContext(device, nullptr); - if (od->context == nullptr) { + if (context == nullptr) { error.Format(openal_output_domain, "Error creating context for \"%s\"", - od->device_name); - alcCloseDevice(od->device); + device_name); + alcCloseDevice(device); return false; } return true; } -static AudioOutput * -openal_init(const config_param ¶m, Error &error) +inline bool +OpenALOutput::Configure(const config_param ¶m, Error &error) { - const char *device_name = param.GetBlockValue("device"); - if (device_name == nullptr) { - device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); - } - - OpenALOutput *od = new OpenALOutput(); - if (!od->Initialize(param, error)) { - delete od; - return nullptr; - } + if (!base.Configure(param, error)) + return false; - od->device_name = device_name; + device_name = param.GetBlockValue("device"); + if (device_name == nullptr) + device_name = alcGetString(nullptr, + ALC_DEFAULT_DEVICE_SPECIFIER); - return &od->base; + return true; } -static void -openal_finish(AudioOutput *ao) +inline OpenALOutput * +OpenALOutput::Create(const config_param ¶m, Error &error) { - OpenALOutput *od = (OpenALOutput *)ao; + OpenALOutput *oo = new OpenALOutput(); + + if (!oo->Configure(param, error)) { + delete oo; + return nullptr; + } - delete od; + return oo; } -static bool -openal_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +OpenALOutput::Open(AudioFormat &audio_format, Error &error) { - OpenALOutput *od = (OpenALOutput *)ao; + format = openal_audio_format(audio_format); - od->format = openal_audio_format(audio_format); - - if (!openal_setup_context(od, error)) { + if (!SetupContext(error)) return false; - } - alcMakeContextCurrent(od->context); - alGenBuffers(NUM_BUFFERS, od->buffers); + alcMakeContextCurrent(context); + alGenBuffers(NUM_BUFFERS, buffers); if (alGetError() != AL_NO_ERROR) { error.Set(openal_output_domain, "Failed to generate buffers"); return false; } - alGenSources(1, &od->source); + alGenSources(1, &source); if (alGetError() != AL_NO_ERROR) { error.Set(openal_output_domain, "Failed to generate source"); - alDeleteBuffers(NUM_BUFFERS, od->buffers); + alDeleteBuffers(NUM_BUFFERS, buffers); return false; } - od->filled = 0; - od->frequency = audio_format.sample_rate; + filled = 0; + frequency = audio_format.sample_rate; return true; } -static void -openal_close(AudioOutput *ao) +inline void +OpenALOutput::Close() { - OpenALOutput *od = (OpenALOutput *)ao; - - alcMakeContextCurrent(od->context); - alDeleteSources(1, &od->source); - alDeleteBuffers(NUM_BUFFERS, od->buffers); - alcDestroyContext(od->context); - alcCloseDevice(od->device); + alcMakeContextCurrent(context); + alDeleteSources(1, &source); + alDeleteBuffers(NUM_BUFFERS, buffers); + alcDestroyContext(context); + alcCloseDevice(device); } -static unsigned -openal_delay(AudioOutput *ao) +inline size_t +OpenALOutput::Play(const void *chunk, size_t size, gcc_unused Error &error) { - OpenALOutput *od = (OpenALOutput *)ao; - - return od->filled < NUM_BUFFERS || openal_has_processed(od) - ? 0 - /* we don't know exactly how long we must wait for the - next buffer to finish, so this is a random - guess: */ - : 50; -} + if (alcGetCurrentContext() != context) + alcMakeContextCurrent(context); -static size_t -openal_play(AudioOutput *ao, const void *chunk, size_t size, - gcc_unused Error &error) -{ - OpenALOutput *od = (OpenALOutput *)ao; ALuint buffer; - - if (alcGetCurrentContext() != od->context) { - alcMakeContextCurrent(od->context); - } - - if (od->filled < NUM_BUFFERS) { + if (filled < NUM_BUFFERS) { /* fill all buffers */ - buffer = od->buffers[od->filled]; - od->filled++; + buffer = buffers[filled]; + filled++; } else { /* wait for processed buffer */ - while (!openal_has_processed(od)) + while (!HasProcessed()) usleep(10); - alSourceUnqueueBuffers(od->source, 1, &buffer); + alSourceUnqueueBuffers(source, 1, &buffer); } - alBufferData(buffer, od->format, chunk, size, od->frequency); - alSourceQueueBuffers(od->source, 1, &buffer); + alBufferData(buffer, format, chunk, size, frequency); + alSourceQueueBuffers(source, 1, &buffer); - if (!openal_is_playing(od)) - alSourcePlay(od->source); + if (!IsPlaying()) + alSourcePlay(source); return size; } -static void -openal_cancel(AudioOutput *ao) +inline void +OpenALOutput::Cancel() { - OpenALOutput *od = (OpenALOutput *)ao; - - od->filled = 0; - alcMakeContextCurrent(od->context); - alSourceStop(od->source); + filled = 0; + alcMakeContextCurrent(context); + alSourceStop(source); /* force-unqueue all buffers */ - alSourcei(od->source, AL_BUFFER, 0); - od->filled = 0; + alSourcei(source, AL_BUFFER, 0); + filled = 0; } +typedef AudioOutputWrapper<OpenALOutput> Wrapper; + const struct AudioOutputPlugin openal_output_plugin = { "openal", nullptr, - openal_init, - openal_finish, + &Wrapper::Init, + &Wrapper::Finish, nullptr, nullptr, - openal_open, - openal_close, - openal_delay, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - openal_play, + &Wrapper::Play, nullptr, - openal_cancel, + &Wrapper::Cancel, nullptr, nullptr, }; diff --git a/src/output/plugins/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx index a27e6b53c..abf4ffdb1 100644 --- a/src/output/plugins/OpenALOutputPlugin.hxx +++ b/src/output/plugins/OpenALOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx index 39d87fc35..eece76a61 100644 --- a/src/output/plugins/OssOutputPlugin.cxx +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -124,7 +124,7 @@ oss_stat_device(const char *device, int *errno_r) return OSS_STAT_NO_ERROR; } -static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; +static const char *const default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; static bool oss_output_test_default_device(void) @@ -380,7 +380,7 @@ oss_setup_sample_rate(int fd, AudioFormat &audio_format, break; } - static const int sample_rates[] = { 48000, 44100, 0 }; + static constexpr 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) @@ -572,7 +572,7 @@ oss_setup_sample_format(int fd, AudioFormat &audio_format, /* the requested sample format is not available - probe for other formats supported by MPD */ - static const SampleFormat sample_formats[] = { + static constexpr SampleFormat sample_formats[] = { SampleFormat::S24_P32, SampleFormat::S32, SampleFormat::S16, diff --git a/src/output/plugins/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx index f9970c8f0..8f9b3d424 100644 --- a/src/output/plugins/OssOutputPlugin.hxx +++ b/src/output/plugins/OssOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx index 7a1f32258..ea9300cf3 100644 --- a/src/output/plugins/PipeOutputPlugin.cxx +++ b/src/output/plugins/PipeOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "PipeOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "config/ConfigError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -28,7 +29,9 @@ #include <stdio.h> -struct PipeOutput { +class PipeOutput { + friend struct AudioOutputWrapper<PipeOutput>; + AudioOutput base; std::string cmd; @@ -37,11 +40,19 @@ struct PipeOutput { PipeOutput() :base(pipe_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error) { - return base.Configure(param, error); + bool Configure(const config_param ¶m, Error &error); + +public: + static PipeOutput *Create(const config_param ¶m, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + + void Close() { + pclose(fh); } - bool Configure(const config_param ¶m, Error &error); + size_t Play(const void *chunk, size_t size, Error &error); + }; static constexpr Domain pipe_output_domain("pipe_output"); @@ -49,6 +60,9 @@ static constexpr Domain pipe_output_domain("pipe_output"); inline bool PipeOutput::Configure(const config_param ¶m, Error &error) { + if (!base.Configure(param, error)) + return false; + cmd = param.GetBlockValue("command", ""); if (cmd.empty()) { error.Set(config_domain, @@ -59,83 +73,56 @@ PipeOutput::Configure(const config_param ¶m, Error &error) return true; } -static AudioOutput * -pipe_output_init(const config_param ¶m, Error &error) +inline PipeOutput * +PipeOutput::Create(const config_param ¶m, Error &error) { - PipeOutput *pd = new PipeOutput(); - - if (!pd->Initialize(param, error)) { - delete pd; - return nullptr; - } + PipeOutput *po = new PipeOutput(); - if (!pd->Configure(param, error)) { - delete pd; + if (!po->Configure(param, error)) { + delete po; return nullptr; } - return &pd->base; + return po; } -static void -pipe_output_finish(AudioOutput *ao) -{ - PipeOutput *pd = (PipeOutput *)ao; - - delete pd; -} - -static bool -pipe_output_open(AudioOutput *ao, - gcc_unused AudioFormat &audio_format, - Error &error) +inline bool +PipeOutput::Open(gcc_unused AudioFormat &audio_format, Error &error) { - PipeOutput *pd = (PipeOutput *)ao; - - pd->fh = popen(pd->cmd.c_str(), "w"); - if (pd->fh == nullptr) { + fh = popen(cmd.c_str(), "w"); + if (fh == nullptr) { error.FormatErrno("Error opening pipe \"%s\"", - pd->cmd.c_str()); + cmd.c_str()); return false; } return true; } -static void -pipe_output_close(AudioOutput *ao) +inline size_t +PipeOutput::Play(const void *chunk, size_t size, Error &error) { - PipeOutput *pd = (PipeOutput *)ao; - - pclose(pd->fh); -} - -static size_t -pipe_output_play(AudioOutput *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) + size_t nbytes = fwrite(chunk, 1, size, fh); + if (nbytes == 0) error.SetErrno("Write error on pipe"); - return ret; + return nbytes; } +typedef AudioOutputWrapper<PipeOutput> Wrapper; + const struct AudioOutputPlugin pipe_output_plugin = { "pipe", nullptr, - pipe_output_init, - pipe_output_finish, + &Wrapper::Init, + &Wrapper::Finish, nullptr, nullptr, - pipe_output_open, - pipe_output_close, + &Wrapper::Open, + &Wrapper::Close, nullptr, nullptr, - pipe_output_play, + &Wrapper::Play, nullptr, nullptr, nullptr, diff --git a/src/output/plugins/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx index bdaf2ecfd..5d92848c2 100644 --- a/src/output/plugins/PipeOutputPlugin.hxx +++ b/src/output/plugins/PipeOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx index 120bad090..8ad885f82 100644 --- a/src/output/plugins/PulseOutputPlugin.cxx +++ b/src/output/plugins/PulseOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,14 @@ #include "config.h" #include "PulseOutputPlugin.hxx" +#include "lib/pulse/Domain.hxx" +#include "lib/pulse/Error.hxx" +#include "lib/pulse/LogError.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "mixer/MixerList.hxx" #include "mixer/plugins/PulseMixerPlugin.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" #include "Log.hxx" #include <pulse/thread-mainloop.h> @@ -31,7 +34,6 @@ #include <pulse/stream.h> #include <pulse/introspect.h> #include <pulse/subscribe.h> -#include <pulse/error.h> #include <pulse/version.h> #include <assert.h> @@ -40,7 +42,9 @@ #define MPD_PULSE_NAME "Music Player Daemon" -struct PulseOutput { +class PulseOutput { + friend struct AudioOutputWrapper<PulseOutput>; + AudioOutput base; const char *name; @@ -56,80 +60,190 @@ struct PulseOutput { size_t writable; PulseOutput() - :base(pulse_output_plugin) {} -}; + :base(pulse_output_plugin), + mixer(nullptr), + mainloop(nullptr), stream(nullptr) {} -static constexpr Domain pulse_output_domain("pulse_output"); +public: + void SetMixer(PulseMixer &_mixer); -static void -SetError(Error &error, pa_context *context, const char *msg) -{ - const int e = pa_context_errno(context); - error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e)); -} + void ClearMixer(gcc_unused PulseMixer &old_mixer) { + assert(mixer == &old_mixer); + + mixer = nullptr; + } + + bool SetVolume(const pa_cvolume &volume, Error &error); + + void Lock() { + pa_threaded_mainloop_lock(mainloop); + } + + void Unlock() { + pa_threaded_mainloop_unlock(mainloop); + } + + void OnContextStateChanged(pa_context_state_t new_state); + void OnServerLayoutChanged(pa_subscription_event_type_t t, + uint32_t idx); + void OnStreamSuspended(pa_stream *_stream); + void OnStreamStateChanged(pa_stream *_stream, + pa_stream_state_t new_state); + void OnStreamWrite(size_t nbytes); + + void OnStreamSuccess() { + Signal(); + } + + gcc_const + static bool TestDefaultDevice(); + + bool Configure(const config_param ¶m, Error &error); + static PulseOutput *Create(const config_param ¶m, Error &error); + + bool Enable(Error &error); + void Disable(); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + unsigned Delay(); + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); + bool Pause(); + +private: + /** + * Attempt to connect asynchronously to the PulseAudio server. + * + * @return true on success, false on error + */ + bool Connect(Error &error); + + /** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ + bool SetupContext(Error &error); + + /** + * Frees and clears the context. + * + * Caller must lock the main loop. + */ + void DeleteContext(); + + void Signal() { + pa_threaded_mainloop_signal(mainloop, 0); + } + + /** + * Check if the context is (already) connected, and waits if + * not. If the context has been disconnected, retry to + * connect. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ + bool WaitConnection(Error &error); + + /** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ + bool SetupStream(const pa_sample_spec &ss, Error &error); + + /** + * Frees and clears the stream. + */ + void DeleteStream(); + + /** + * Check if the stream is (already) connected, and waits if + * not. The mainloop must be locked before calling this + * function. + * + * @return true on success, false on error + */ + bool WaitStream(Error &error); + + /** + * Sets cork mode on the stream. + */ + bool StreamPause(bool pause, Error &error); +}; void pulse_output_lock(PulseOutput &po) { - pa_threaded_mainloop_lock(po.mainloop); + po.Lock(); } void pulse_output_unlock(PulseOutput &po) { - pa_threaded_mainloop_unlock(po.mainloop); + po.Unlock(); } -void -pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm) +inline void +PulseOutput::SetMixer(PulseMixer &_mixer) { - assert(po.mixer == nullptr); + assert(mixer == nullptr); - po.mixer = ± + mixer = &_mixer; - if (po.mainloop == nullptr) + if (mainloop == nullptr) return; - pa_threaded_mainloop_lock(po.mainloop); + pa_threaded_mainloop_lock(mainloop); - if (po.context != nullptr && - pa_context_get_state(po.context) == PA_CONTEXT_READY) { - pulse_mixer_on_connect(pm, po.context); + if (context != nullptr && + pa_context_get_state(context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(_mixer, context); - if (po.stream != nullptr && - pa_stream_get_state(po.stream) == PA_STREAM_READY) - pulse_mixer_on_change(pm, po.context, po.stream); + if (stream != nullptr && + pa_stream_get_state(stream) == PA_STREAM_READY) + pulse_mixer_on_change(_mixer, context, stream); } - pa_threaded_mainloop_unlock(po.mainloop); + pa_threaded_mainloop_unlock(mainloop); } void -pulse_output_clear_mixer(PulseOutput &po, gcc_unused PulseMixer &pm) +pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm) { - assert(po.mixer == &pm); - - po.mixer = nullptr; + po.SetMixer(pm); } -bool -pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, - Error &error) +void +pulse_output_clear_mixer(PulseOutput &po, PulseMixer &pm) { - pa_operation *o; + po.ClearMixer(pm); +} - if (po.context == nullptr || po.stream == nullptr || - pa_stream_get_state(po.stream) != PA_STREAM_READY) { - error.Set(pulse_output_domain, "disconnected"); +inline bool +PulseOutput::SetVolume(const pa_cvolume &volume, Error &error) +{ + if (context == nullptr || stream == nullptr || + pa_stream_get_state(stream) != PA_STREAM_READY) { + error.Set(pulse_domain, "disconnected"); return false; } - o = pa_context_set_sink_input_volume(po.context, - pa_stream_get_index(po.stream), - volume, nullptr, nullptr); + pa_operation *o = + pa_context_set_sink_input_volume(context, + pa_stream_get_index(stream), + &volume, nullptr, nullptr); if (o == nullptr) { - SetError(error, po.context, - "failed to set PulseAudio volume"); + SetPulseError(error, context, + "failed to set PulseAudio volume"); return false; } @@ -137,6 +251,13 @@ pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, return true; } +bool +pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, + Error &error) +{ + return po.SetVolume(*volume, error); +} + /** * \brief waits for a pulseaudio operation to finish, frees it and * unlocks the mainloop @@ -169,32 +290,30 @@ static void pulse_output_stream_success_cb(gcc_unused pa_stream *s, gcc_unused int success, void *userdata) { - PulseOutput *po = (PulseOutput *)userdata; + PulseOutput &po = *(PulseOutput *)userdata; - pa_threaded_mainloop_signal(po->mainloop, 0); + po.OnStreamSuccess(); } -static void -pulse_output_context_state_cb(struct pa_context *context, void *userdata) +inline void +PulseOutput::OnContextStateChanged(pa_context_state_t new_state) { - PulseOutput *po = (PulseOutput *)userdata; - - switch (pa_context_get_state(context)) { + switch (new_state) { case PA_CONTEXT_READY: - if (po->mixer != nullptr) - pulse_mixer_on_connect(*po->mixer, context); + if (mixer != nullptr) + pulse_mixer_on_connect(*mixer, context); - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: - if (po->mixer != nullptr) - pulse_mixer_on_disconnect(*po->mixer); + if (mixer != nullptr) + pulse_mixer_on_disconnect(*mixer); /* the caller thread might be waiting for these states */ - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_CONTEXT_UNCONNECTED: @@ -206,230 +325,203 @@ pulse_output_context_state_cb(struct pa_context *context, void *userdata) } static void -pulse_output_subscribe_cb(pa_context *context, - pa_subscription_event_type_t t, - uint32_t idx, void *userdata) +pulse_output_context_state_cb(struct pa_context *context, void *userdata) +{ + PulseOutput &po = *(PulseOutput *)userdata; + + po.OnContextStateChanged(pa_context_get_state(context)); +} + +inline void +PulseOutput::OnServerLayoutChanged(pa_subscription_event_type_t t, + uint32_t idx) { - 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 != nullptr && + if (mixer != nullptr && facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && - po->stream != nullptr && - pa_stream_get_state(po->stream) == PA_STREAM_READY && - idx == pa_stream_get_index(po->stream) && + stream != nullptr && + pa_stream_get_state(stream) == PA_STREAM_READY && + idx == pa_stream_get_index(stream) && (type == PA_SUBSCRIPTION_EVENT_NEW || type == PA_SUBSCRIPTION_EVENT_CHANGE)) - pulse_mixer_on_change(*po->mixer, context, po->stream); + pulse_mixer_on_change(*mixer, context, stream); } -/** - * Attempt to connect asynchronously to the PulseAudio server. - * - * @return true on success, false on error - */ -static bool -pulse_output_connect(PulseOutput *po, Error &error) +static void +pulse_output_subscribe_cb(gcc_unused pa_context *context, + pa_subscription_event_type_t t, + uint32_t idx, void *userdata) { - assert(po != nullptr); - assert(po->context != nullptr); + PulseOutput &po = *(PulseOutput *)userdata; - if (pa_context_connect(po->context, po->server, + po.OnServerLayoutChanged(t, idx); +} + +inline bool +PulseOutput::Connect(Error &error) +{ + assert(context != nullptr); + + if (pa_context_connect(context, server, (pa_context_flags_t)0, nullptr) < 0) { - SetError(error, po->context, - "pa_context_connect() has failed"); + SetPulseError(error, context, + "pa_context_connect() has failed"); return false; } return true; } -/** - * Frees and clears the stream. - */ -static void -pulse_output_delete_stream(PulseOutput *po) +void +PulseOutput::DeleteStream() { - assert(po != nullptr); - assert(po->stream != nullptr); + assert(stream != nullptr); - pa_stream_set_suspended_callback(po->stream, nullptr, nullptr); + pa_stream_set_suspended_callback(stream, nullptr, nullptr); - pa_stream_set_state_callback(po->stream, nullptr, nullptr); - pa_stream_set_write_callback(po->stream, nullptr, nullptr); + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_disconnect(po->stream); - pa_stream_unref(po->stream); - po->stream = nullptr; + pa_stream_disconnect(stream); + pa_stream_unref(stream); + stream = nullptr; } -/** - * Frees and clears the context. - * - * Caller must lock the main loop. - */ -static void -pulse_output_delete_context(PulseOutput *po) +void +PulseOutput::DeleteContext() { - assert(po != nullptr); - assert(po->context != nullptr); + assert(context != nullptr); - pa_context_set_state_callback(po->context, nullptr, nullptr); - pa_context_set_subscribe_callback(po->context, nullptr, nullptr); + pa_context_set_state_callback(context, nullptr, nullptr); + pa_context_set_subscribe_callback(context, nullptr, nullptr); - pa_context_disconnect(po->context); - pa_context_unref(po->context); - po->context = nullptr; + pa_context_disconnect(context); + pa_context_unref(context); + context = nullptr; } -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_context(PulseOutput *po, Error &error) +bool +PulseOutput::SetupContext(Error &error) { - assert(po != nullptr); - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), - MPD_PULSE_NAME); - if (po->context == nullptr) { - error.Set(pulse_output_domain, "pa_context_new() has failed"); + context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), + MPD_PULSE_NAME); + if (context == nullptr) { + error.Set(pulse_domain, "pa_context_new() has failed"); return false; } - pa_context_set_state_callback(po->context, - pulse_output_context_state_cb, po); - pa_context_set_subscribe_callback(po->context, - pulse_output_subscribe_cb, po); + pa_context_set_state_callback(context, + pulse_output_context_state_cb, this); + pa_context_set_subscribe_callback(context, + pulse_output_subscribe_cb, this); - if (!pulse_output_connect(po, error)) { - pulse_output_delete_context(po); + if (!Connect(error)) { + DeleteContext(); return false; } return true; } -static AudioOutput * -pulse_output_init(const config_param ¶m, Error &error) +inline bool +PulseOutput::Configure(const config_param ¶m, Error &error) { - PulseOutput *po; + if (!base.Configure(param, error)) + return false; + + name = param.GetBlockValue("name", "mpd_pulse"); + server = param.GetBlockValue("server"); + sink = param.GetBlockValue("sink"); + return true; +} + +PulseOutput * +PulseOutput::Create(const config_param ¶m, Error &error) +{ setenv("PULSE_PROP_media.role", "music", true); setenv("PULSE_PROP_application.icon_name", "mpd", true); - po = new PulseOutput(); - if (!po->base.Configure(param, error)) { + auto *po = new PulseOutput(); + if (!po->Configure(param, error)) { delete po; return nullptr; } - po->name = param.GetBlockValue("name", "mpd_pulse"); - po->server = param.GetBlockValue("server"); - po->sink = param.GetBlockValue("sink"); - - po->mixer = nullptr; - po->mainloop = nullptr; - po->context = nullptr; - po->stream = nullptr; - - return &po->base; + return po; } -static void -pulse_output_finish(AudioOutput *ao) +inline bool +PulseOutput::Enable(Error &error) { - PulseOutput *po = (PulseOutput *)ao; - - delete po; -} - -static bool -pulse_output_enable(AudioOutput *ao, Error &error) -{ - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop == nullptr); - assert(po->context == nullptr); + assert(mainloop == nullptr); /* create the libpulse mainloop and start the thread */ - po->mainloop = pa_threaded_mainloop_new(); - if (po->mainloop == nullptr) { - error.Set(pulse_output_domain, + mainloop = pa_threaded_mainloop_new(); + if (mainloop == nullptr) { + error.Set(pulse_domain, "pa_threaded_mainloop_new() has failed"); return false; } - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (pa_threaded_mainloop_start(po->mainloop) < 0) { - pa_threaded_mainloop_unlock(po->mainloop); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; + if (pa_threaded_mainloop_start(mainloop) < 0) { + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_free(mainloop); + mainloop = nullptr; - error.Set(pulse_output_domain, + error.Set(pulse_domain, "pa_threaded_mainloop_start() has failed"); return false; } /* create the libpulse context and connect it */ - 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 = nullptr; + if (!SetupContext(error)) { + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + mainloop = nullptr; return false; } - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); return true; } -static void -pulse_output_disable(AudioOutput *ao) +inline void +PulseOutput::Disable() { - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - pa_threaded_mainloop_stop(po->mainloop); - if (po->context != nullptr) - pulse_output_delete_context(po); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; + pa_threaded_mainloop_stop(mainloop); + if (context != nullptr) + DeleteContext(); + pa_threaded_mainloop_free(mainloop); + mainloop = nullptr; } -/** - * Check if the context is (already) connected, and waits if not. If - * the context has been disconnected, retry to connect. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_wait_connection(PulseOutput *po, Error &error) +bool +PulseOutput::WaitConnection(Error &error) { - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); pa_context_state_t state; - if (po->context == nullptr && !pulse_output_setup_context(po, error)) + if (context == nullptr && !SetupContext(error)) return false; while (true) { - state = pa_context_get_state(po->context); + state = pa_context_get_state(context); switch (state) { case PA_CONTEXT_READY: /* nothing to do */ @@ -439,56 +531,61 @@ pulse_output_wait_connection(PulseOutput *po, Error &error) case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: /* failure */ - SetError(error, po->context, "failed to connect"); - pulse_output_delete_context(po); + SetPulseError(error, context, "failed to connect"); + DeleteContext(); return false; case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: /* wait some more */ - pa_threaded_mainloop_wait(po->mainloop); + pa_threaded_mainloop_wait(mainloop); break; } } } -static void -pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata) +inline void +PulseOutput::OnStreamSuspended(gcc_unused pa_stream *_stream) { - PulseOutput *po = (PulseOutput *)userdata; - - assert(stream == po->stream || po->stream == nullptr); - assert(po->mainloop != nullptr); + assert(_stream == stream || stream == nullptr); + assert(mainloop != nullptr); /* wake up the main loop to break out of the loop in pulse_output_play() */ - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); } static void -pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +pulse_output_stream_suspended_cb(pa_stream *stream, void *userdata) { - PulseOutput *po = (PulseOutput *)userdata; + PulseOutput &po = *(PulseOutput *)userdata; - assert(stream == po->stream || po->stream == nullptr); - assert(po->mainloop != nullptr); - assert(po->context != nullptr); + po.OnStreamSuspended(stream); +} + +inline void +PulseOutput::OnStreamStateChanged(pa_stream *_stream, + pa_stream_state_t new_state) +{ + assert(_stream == stream || stream == nullptr); + assert(mainloop != nullptr); + assert(context != nullptr); - switch (pa_stream_get_state(stream)) { + switch (new_state) { case PA_STREAM_READY: - if (po->mixer != nullptr) - pulse_mixer_on_change(*po->mixer, po->context, stream); + if (mixer != nullptr) + pulse_mixer_on_change(*mixer, context, _stream); - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: - if (po->mixer != nullptr) - pulse_mixer_on_disconnect(*po->mixer); + if (mixer != nullptr) + pulse_mixer_on_disconnect(*mixer); - pa_threaded_mainloop_signal(po->mainloop, 0); + Signal(); break; case PA_STREAM_UNCONNECTED: @@ -498,68 +595,75 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata) } static void +pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +{ + PulseOutput &po = *(PulseOutput *)userdata; + + return po.OnStreamStateChanged(stream, pa_stream_get_state(stream)); +} + +inline void +PulseOutput::OnStreamWrite(size_t nbytes) +{ + assert(mainloop != nullptr); + + writable = nbytes; + Signal(); +} + +static void pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes, void *userdata) { - PulseOutput *po = (PulseOutput *)userdata; - - assert(po->mainloop != nullptr); + PulseOutput &po = *(PulseOutput *)userdata; - po->writable = nbytes; - pa_threaded_mainloop_signal(po->mainloop, 0); + return po.OnStreamWrite(nbytes); } -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, - Error &error) +inline bool +PulseOutput::SetupStream(const pa_sample_spec &ss, Error &error) { - assert(po != nullptr); - assert(po->context != nullptr); + assert(context != nullptr); - po->stream = pa_stream_new(po->context, po->name, ss, nullptr); - if (po->stream == nullptr) { - SetError(error, po->context, "pa_stream_new() has failed"); + /* WAVE-EX is been adopted as the speaker map for most media files */ + pa_channel_map chan_map; + pa_channel_map_init_auto(&chan_map, ss.channels, + PA_CHANNEL_MAP_WAVEEX); + stream = pa_stream_new(context, name, &ss, &chan_map); + if (stream == nullptr) { + SetPulseError(error, context, + "pa_stream_new() has failed"); return false; } - pa_stream_set_suspended_callback(po->stream, - pulse_output_stream_suspended_cb, po); + pa_stream_set_suspended_callback(stream, + pulse_output_stream_suspended_cb, + this); - pa_stream_set_state_callback(po->stream, - pulse_output_stream_state_cb, po); - pa_stream_set_write_callback(po->stream, - pulse_output_stream_write_cb, po); + pa_stream_set_state_callback(stream, + pulse_output_stream_state_cb, this); + pa_stream_set_write_callback(stream, + pulse_output_stream_write_cb, this); return true; } -static bool -pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +PulseOutput::Open(AudioFormat &audio_format, Error &error) { - PulseOutput *po = (PulseOutput *)ao; - pa_sample_spec ss; - - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (po->context != nullptr) { - switch (pa_context_get_state(po->context)) { + if (context != nullptr) { + switch (pa_context_get_state(context)) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: /* the connection was closed meanwhile; delete it, and pulse_output_wait_connection() will reopen it */ - pulse_output_delete_context(po); + DeleteContext(); break; case PA_CONTEXT_READY: @@ -570,8 +674,8 @@ pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, } } - if (!pulse_output_wait_connection(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!WaitConnection(error)) { + pa_threaded_mainloop_unlock(mainloop); return false; } @@ -579,304 +683,282 @@ pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, we just force MPD to send us everything as 16 bit */ audio_format.format = SampleFormat::S16; + pa_sample_spec ss; ss.format = PA_SAMPLE_S16NE; ss.rate = audio_format.sample_rate; ss.channels = audio_format.channels; /* create a stream .. */ - if (!pulse_output_setup_stream(po, &ss, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!SetupStream(ss, error)) { + pa_threaded_mainloop_unlock(mainloop); return false; } /* .. and connect it (asynchronously) */ - if (pa_stream_connect_playback(po->stream, po->sink, + if (pa_stream_connect_playback(stream, sink, nullptr, pa_stream_flags_t(0), nullptr, nullptr) < 0) { - pulse_output_delete_stream(po); + DeleteStream(); - SetError(error, po->context, - "pa_stream_connect_playback() has failed"); - pa_threaded_mainloop_unlock(po->mainloop); + SetPulseError(error, context, + "pa_stream_connect_playback() has failed"); + pa_threaded_mainloop_unlock(mainloop); return false; } - pa_threaded_mainloop_unlock(po->mainloop); - + pa_threaded_mainloop_unlock(mainloop); return true; } -static void -pulse_output_close(AudioOutput *ao) +inline void +PulseOutput::Close() { - PulseOutput *po = (PulseOutput *)ao; - pa_operation *o; - - assert(po->mainloop != nullptr); + assert(mainloop != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { - o = pa_stream_drain(po->stream, - pulse_output_stream_success_cb, po); + if (pa_stream_get_state(stream) == PA_STREAM_READY) { + pa_operation *o = + pa_stream_drain(stream, + pulse_output_stream_success_cb, this); if (o == nullptr) { - FormatWarning(pulse_output_domain, - "pa_stream_drain() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + LogPulseError(context, + "pa_stream_drain() has failed"); } else - pulse_wait_for_operation(po->mainloop, o); + pulse_wait_for_operation(mainloop, o); } - pulse_output_delete_stream(po); + DeleteStream(); - if (po->context != nullptr && - pa_context_get_state(po->context) != PA_CONTEXT_READY) - pulse_output_delete_context(po); + if (context != nullptr && + pa_context_get_state(context) != PA_CONTEXT_READY) + DeleteContext(); - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); } -/** - * Check if the stream is (already) connected, and waits if not. The - * mainloop must be locked before calling this function. - * - * @return true on success, false on error - */ -static bool -pulse_output_wait_stream(PulseOutput *po, Error &error) +bool +PulseOutput::WaitStream(Error &error) { while (true) { - switch (pa_stream_get_state(po->stream)) { + switch (pa_stream_get_state(stream)) { case PA_STREAM_READY: return true; case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: case PA_STREAM_UNCONNECTED: - SetError(error, po->context, - "failed to connect the stream"); + SetPulseError(error, context, + "failed to connect the stream"); return false; case PA_STREAM_CREATING: - pa_threaded_mainloop_wait(po->mainloop); + pa_threaded_mainloop_wait(mainloop); break; } } } -/** - * Sets cork mode on the stream. - */ -static bool -pulse_output_stream_pause(PulseOutput *po, bool pause, - Error &error) +bool +PulseOutput::StreamPause(bool pause, Error &error) { - pa_operation *o; - - assert(po->mainloop != nullptr); - assert(po->context != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(context != nullptr); + assert(stream != nullptr); - o = pa_stream_cork(po->stream, pause, - pulse_output_stream_success_cb, po); + pa_operation *o = pa_stream_cork(stream, pause, + pulse_output_stream_success_cb, this); if (o == nullptr) { - SetError(error, po->context, "pa_stream_cork() has failed"); + SetPulseError(error, context, + "pa_stream_cork() has failed"); return false; } - if (!pulse_wait_for_operation(po->mainloop, o)) { - SetError(error, po->context, "pa_stream_cork() has failed"); + if (!pulse_wait_for_operation(mainloop, o)) { + SetPulseError(error, context, + "pa_stream_cork() has failed"); return false; } return true; } -static unsigned -pulse_output_delay(AudioOutput *ao) +inline unsigned +PulseOutput::Delay() { - PulseOutput *po = (PulseOutput *)ao; - unsigned result = 0; - - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (po->base.pause && pa_stream_is_corked(po->stream) && - pa_stream_get_state(po->stream) == PA_STREAM_READY) + unsigned result = 0; + if (base.pause && pa_stream_is_corked(stream) && + pa_stream_get_state(stream) == PA_STREAM_READY) /* idle while paused */ result = 1000; - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); return result; } -static size_t -pulse_output_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +PulseOutput::Play(const void *chunk, size_t size, Error &error) { - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(stream != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); /* check if the stream is (already) connected */ - if (!pulse_output_wait_stream(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!WaitStream(error)) { + pa_threaded_mainloop_unlock(mainloop); return 0; } - assert(po->context != nullptr); + assert(context != nullptr); /* unpause if previously paused */ - if (pa_stream_is_corked(po->stream) && - !pulse_output_stream_pause(po, false, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (pa_stream_is_corked(stream) && !StreamPause(false, error)) { + pa_threaded_mainloop_unlock(mainloop); return 0; } /* wait until the server allows us to write */ - while (po->writable == 0) { - if (pa_stream_is_suspended(po->stream)) { - pa_threaded_mainloop_unlock(po->mainloop); - error.Set(pulse_output_domain, "suspended"); + while (writable == 0) { + if (pa_stream_is_suspended(stream)) { + pa_threaded_mainloop_unlock(mainloop); + error.Set(pulse_domain, "suspended"); return 0; } - pa_threaded_mainloop_wait(po->mainloop); + pa_threaded_mainloop_wait(mainloop); - if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { - pa_threaded_mainloop_unlock(po->mainloop); - error.Set(pulse_output_domain, "disconnected"); + if (pa_stream_get_state(stream) != PA_STREAM_READY) { + pa_threaded_mainloop_unlock(mainloop); + error.Set(pulse_domain, "disconnected"); return 0; } } /* now write */ - if (size > po->writable) + if (size > writable) /* don't send more than possible */ - size = po->writable; + size = writable; - po->writable -= size; + writable -= size; - int result = pa_stream_write(po->stream, chunk, size, nullptr, + int result = pa_stream_write(stream, chunk, size, nullptr, 0, PA_SEEK_RELATIVE); - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); if (result < 0) { - SetError(error, po->context, "pa_stream_write() failed"); + SetPulseError(error, context, "pa_stream_write() failed"); return 0; } return size; } -static void -pulse_output_cancel(AudioOutput *ao) +inline void +PulseOutput::Cancel() { - PulseOutput *po = (PulseOutput *)ao; - pa_operation *o; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(stream != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); - if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + if (pa_stream_get_state(stream) != PA_STREAM_READY) { /* no need to flush when the stream isn't connected yet */ - pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_unlock(mainloop); return; } - assert(po->context != nullptr); + assert(context != nullptr); - o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); + pa_operation *o = pa_stream_flush(stream, + pulse_output_stream_success_cb, + this); 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); + LogPulseError(context, "pa_stream_flush() has failed"); + pa_threaded_mainloop_unlock(mainloop); return; } - pulse_wait_for_operation(po->mainloop, o); - pa_threaded_mainloop_unlock(po->mainloop); + pulse_wait_for_operation(mainloop, o); + pa_threaded_mainloop_unlock(mainloop); } -static bool -pulse_output_pause(AudioOutput *ao) +inline bool +PulseOutput::Pause() { - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); + assert(mainloop != nullptr); + assert(stream != nullptr); - pa_threaded_mainloop_lock(po->mainloop); + pa_threaded_mainloop_lock(mainloop); /* check if the stream is (already/still) connected */ Error error; - if (!pulse_output_wait_stream(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!WaitStream(error)) { + pa_threaded_mainloop_unlock(mainloop); LogError(error); return false; } - assert(po->context != nullptr); + assert(context != nullptr); /* cork the stream */ - if (!pa_stream_is_corked(po->stream) && - !pulse_output_stream_pause(po, true, error)) { - pa_threaded_mainloop_unlock(po->mainloop); + if (!pa_stream_is_corked(stream) && !StreamPause(true, error)) { + pa_threaded_mainloop_unlock(mainloop); LogError(error); return false; } - pa_threaded_mainloop_unlock(po->mainloop); - + pa_threaded_mainloop_unlock(mainloop); return true; } -static bool -pulse_output_test_default_device(void) +inline bool +PulseOutput::TestDefaultDevice() { - bool success; - const config_param empty; - PulseOutput *po = (PulseOutput *) - pulse_output_init(empty, IgnoreError()); + PulseOutput *po = PulseOutput::Create(empty, IgnoreError()); if (po == nullptr) return false; - success = pulse_output_wait_connection(po, IgnoreError()); - pulse_output_finish(&po->base); - + bool success = po->WaitConnection(IgnoreError()); + delete po; return success; } +static bool +pulse_output_test_default_device(void) +{ + return PulseOutput::TestDefaultDevice(); +} + +typedef AudioOutputWrapper<PulseOutput> Wrapper; + const struct AudioOutputPlugin pulse_output_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, + &Wrapper::Init, + &Wrapper::Finish, + &Wrapper::Enable, + &Wrapper::Disable, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - pulse_output_play, + &Wrapper::Play, nullptr, - pulse_output_cancel, - pulse_output_pause, + &Wrapper::Cancel, + &Wrapper::Pause, &pulse_mixer_plugin, }; diff --git a/src/output/plugins/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx index 9219780a5..f193db1d8 100644 --- a/src/output/plugins/PulseOutputPlugin.hxx +++ b/src/output/plugins/PulseOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX #define MPD_PULSE_OUTPUT_PLUGIN_HXX -struct PulseOutput; +class PulseOutput; class PulseMixer; struct pa_cvolume; class Error; diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx index 87e23f55a..39fbca6e9 100644 --- a/src/output/plugins/RecorderOutputPlugin.cxx +++ b/src/output/plugins/RecorderOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,13 @@ #include "config.h" #include "RecorderOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderList.hxx" #include "config/ConfigError.hxx" +#include "Log.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "system/fd_util.h" @@ -45,7 +49,7 @@ struct RecorderOutput { /** * The destination file name. */ - const char *path; + AllocatedPath path; /** * The destination file descriptor. @@ -58,7 +62,8 @@ struct RecorderOutput { char buffer[32768]; RecorderOutput() - :base(recorder_output_plugin) {} + :base(recorder_output_plugin), + path(AllocatedPath::Null()) {} bool Initialize(const config_param ¶m, Error &error_r) { return base.Configure(param, error_r); @@ -66,12 +71,17 @@ struct RecorderOutput { bool Configure(const config_param ¶m, Error &error); + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + bool WriteToFile(const void *data, size_t length, Error &error); /** * Writes pending data from the encoder to the output file. */ bool EncoderToFile(Error &error); + + void SendTag(const Tag &tag); }; static constexpr Domain recorder_output_domain("recorder_output"); @@ -90,9 +100,10 @@ RecorderOutput::Configure(const config_param ¶m, Error &error) return false; } - path = param.GetBlockValue("path"); - if (path == nullptr) { - error.Set(config_domain, "'path' not configured"); + path = param.GetBlockPath("path", error); + if (path.IsNull()) { + if (!error.IsDefined()) + error.Set(config_domain, "'path' not configured"); return false; } @@ -151,7 +162,8 @@ RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error) "write() returned 0"); return false; } else if (errno != EINTR) { - error.FormatErrno("Failed to write to '%s'", path); + error.FormatErrno("Failed to write to '%s'", + path.c_str()); return false; } } @@ -176,56 +188,68 @@ RecorderOutput::EncoderToFile(Error &error) } } -static bool -recorder_output_open(AudioOutput *ao, - AudioFormat &audio_format, - Error &error) +inline bool +RecorderOutput::Open(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); + fd = OpenFile(path, + O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, + 0666); + if (fd < 0) { + error.FormatErrno("Failed to create '%s'", path.c_str()); return false; } /* open the encoder */ - if (!encoder_open(recorder->encoder, audio_format, error)) { - close(recorder->fd); - unlink(recorder->path); + if (!encoder_open(encoder, audio_format, error)) { + close(fd); + RemoveFile(path); return false; } - if (!recorder->EncoderToFile(error)) { - encoder_close(recorder->encoder); - close(recorder->fd); - unlink(recorder->path); + if (!EncoderToFile(error)) { + encoder_close(encoder); + close(fd); + RemoveFile(path); return false; } return true; } -static void -recorder_output_close(AudioOutput *ao) +inline void +RecorderOutput::Close() { - RecorderOutput *recorder = (RecorderOutput *)ao; - /* flush the encoder and write the rest to the file */ - if (encoder_end(recorder->encoder, IgnoreError())) - recorder->EncoderToFile(IgnoreError()); + if (encoder_end(encoder, IgnoreError())) + EncoderToFile(IgnoreError()); /* now really close everything */ - encoder_close(recorder->encoder); + encoder_close(encoder); - close(recorder->fd); + close(fd); +} + +inline void +RecorderOutput::SendTag(const Tag &tag) +{ + Error error; + if (!encoder_pre_tag(encoder, error) || + !EncoderToFile(error) || + !encoder_tag(encoder, tag, error)) + LogError(error); +} + +static void +recorder_output_send_tag(AudioOutput *ao, const Tag &tag) +{ + RecorderOutput &recorder = *(RecorderOutput *)ao; + + recorder.SendTag(tag); } static size_t @@ -239,6 +263,8 @@ recorder_output_play(AudioOutput *ao, const void *chunk, size_t size, ? size : 0; } +typedef AudioOutputWrapper<RecorderOutput> Wrapper; + const struct AudioOutputPlugin recorder_output_plugin = { "recorder", nullptr, @@ -246,10 +272,10 @@ const struct AudioOutputPlugin recorder_output_plugin = { recorder_output_finish, nullptr, nullptr, - recorder_output_open, - recorder_output_close, - nullptr, + &Wrapper::Open, + &Wrapper::Close, nullptr, + recorder_output_send_tag, recorder_output_play, nullptr, nullptr, diff --git a/src/output/plugins/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx index ea1044e0f..061279fba 100644 --- a/src/output/plugins/RecorderOutputPlugin.hxx +++ b/src/output/plugins/RecorderOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx index aa37c91b7..c0a429198 100644 --- a/src/output/plugins/RoarOutputPlugin.cxx +++ b/src/output/plugins/RoarOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen * @@ -21,6 +21,7 @@ #include "config.h" #include "RoarOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "../Wrapper.hxx" #include "mixer/MixerList.hxx" #include "thread/Mutex.hxx" #include "util/Error.hxx" @@ -36,6 +37,8 @@ #undef new class RoarOutput { + friend struct AudioOutputWrapper<RoarOutput>; + AudioOutput base; std::string host, name; @@ -147,14 +150,6 @@ roar_init(const config_param ¶m, Error &error) } static void -roar_finish(AudioOutput *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - delete self; -} - -static void roar_use_audio_format(struct roar_audio_info *info, AudioFormat &audio_format) { @@ -221,14 +216,6 @@ RoarOutput::Open(AudioFormat &audio_format, Error &error) return true; } -static bool -roar_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - - return self->Open(audio_format, error); -} - inline void RoarOutput::Close() { @@ -242,13 +229,6 @@ RoarOutput::Close() roar_disconnect(&con); } -static void -roar_close(AudioOutput *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - self->Close(); -} - inline void RoarOutput::Cancel() { @@ -277,14 +257,6 @@ RoarOutput::Cancel() alive = true; } -static void -roar_cancel(AudioOutput *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - self->Cancel(); -} - inline size_t RoarOutput::Play(const void *chunk, size_t size, Error &error) { @@ -302,14 +274,6 @@ RoarOutput::Play(const void *chunk, size_t size, Error &error) return nbytes; } -static size_t -roar_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - return self->Play(chunk, size, error); -} - static const char* roar_tag_convert(TagType type, bool *is_uuid) { @@ -407,26 +371,28 @@ RoarOutput::SendTag(const Tag &tag) } static void -roar_send_tag(AudioOutput *ao, const Tag *meta) +roar_send_tag(AudioOutput *ao, const Tag &meta) { RoarOutput *self = (RoarOutput *)ao; - self->SendTag(*meta); + self->SendTag(meta); } +typedef AudioOutputWrapper<RoarOutput> Wrapper; + const struct AudioOutputPlugin roar_output_plugin = { "roar", nullptr, roar_init, - roar_finish, + &Wrapper::Finish, nullptr, nullptr, - roar_open, - roar_close, + &Wrapper::Open, + &Wrapper::Close, nullptr, roar_send_tag, - roar_play, + &Wrapper::Play, nullptr, - roar_cancel, + &Wrapper::Cancel, nullptr, &roar_mixer_plugin, }; diff --git a/src/output/plugins/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx index 5f5a9246e..a9726d5c8 100644 --- a/src/output/plugins/RoarOutputPlugin.hxx +++ b/src/output/plugins/RoarOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx index b51f7ed82..68c44605e 100644 --- a/src/output/plugins/ShoutOutputPlugin.cxx +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -462,7 +462,7 @@ my_shout_pause(AudioOutput *ao) } static void -shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) +shout_tag_to_metadata(const Tag &tag, char *dest, size_t size) { char artist[size]; char title[size]; @@ -470,7 +470,7 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) artist[0] = 0; title[0] = 0; - for (const auto &item : *tag) { + for (const auto &item : tag) { switch (item.type) { case TAG_ARTIST: strncpy(artist, item.value, size); @@ -488,7 +488,7 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) } static void my_shout_set_tag(AudioOutput *ao, - const Tag *tag) + const Tag &tag) { ShoutOutput *sd = (ShoutOutput *)ao; diff --git a/src/output/plugins/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx index 9f706fc3b..b103b3bf0 100644 --- a/src/output/plugins/ShoutOutputPlugin.hxx +++ b/src/output/plugins/ShoutOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx index 30745f97c..15a8707fe 100644 --- a/src/output/plugins/SolarisOutputPlugin.cxx +++ b/src/output/plugins/SolarisOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx index 3f9ede7a6..f6f32504a 100644 --- a/src/output/plugins/SolarisOutputPlugin.hxx +++ b/src/output/plugins/SolarisOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx index e5c5a6f0c..6b8f62844 100644 --- a/src/output/plugins/WinmmOutputPlugin.cxx +++ b/src/output/plugins/WinmmOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -56,6 +56,18 @@ struct WinmmOutput { static constexpr Domain winmm_output_domain("winmm_output"); +static void +SetWaveOutError(Error &error, MMRESULT result, const char *prefix) +{ + char buffer[256]; + if (waveOutGetErrorTextA(result, buffer, + ARRAY_SIZE(buffer)) == MMSYSERR_NOERROR) + error.Format(winmm_output_domain, int(result), + "%s: %s", prefix, buffer); + else + error.Set(winmm_output_domain, int(result), prefix); +} + HWAVEOUT winmm_output_get_handle(WinmmOutput &output) { @@ -179,7 +191,7 @@ winmm_output_open(AudioOutput *ao, AudioFormat &audio_format, (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); if (result != MMSYSERR_NOERROR) { CloseHandle(wo->event); - error.Set(winmm_output_domain, "waveOutOpen() failed"); + SetWaveOutError(error, result, "waveOutOpen() failed"); return false; } @@ -225,8 +237,8 @@ winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); if (result != MMSYSERR_NOERROR) { - error.Set(winmm_output_domain, result, - "waveOutPrepareHeader() failed"); + SetWaveOutError(error, result, + "waveOutPrepareHeader() failed"); return false; } @@ -251,8 +263,8 @@ winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, if (result == MMSYSERR_NOERROR) return true; else if (result != WAVERR_STILLPLAYING) { - error.Set(winmm_output_domain, result, - "waveOutUnprepareHeader() failed"); + SetWaveOutError(error, result, + "waveOutUnprepareHeader() failed"); return false; } @@ -278,8 +290,7 @@ winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) if (result != MMSYSERR_NOERROR) { waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); - error.Set(winmm_output_domain, result, - "waveOutWrite() failed"); + SetWaveOutError(error, result, "waveOutWrite() failed"); return 0; } diff --git a/src/output/plugins/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx index 50fae4f2f..6c8fcc627 100644 --- a/src/output/plugins/WinmmOutputPlugin.hxx +++ b/src/output/plugins/WinmmOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx index 3797c3d26..b372eedbc 100644 --- a/src/output/plugins/httpd/HttpdClient.cxx +++ b/src/output/plugins/httpd/HttpdClient.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx index f94f05769..af83f0d52 100644 --- a/src/output/plugins/httpd/HttpdClient.hxx +++ b/src/output/plugins/httpd/HttpdClient.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx index 20ff15e42..6e659c703 100644 --- a/src/output/plugins/httpd/HttpdInternal.hxx +++ b/src/output/plugins/httpd/HttpdInternal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -153,7 +153,7 @@ public: HttpdOutput(EventLoop &_loop); ~HttpdOutput(); -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) constexpr #endif static HttpdOutput *Cast(AudioOutput *ao) { @@ -250,7 +250,7 @@ public: bool EncodeAndPlay(const void *chunk, size_t size, Error &error); - void SendTag(const Tag *tag); + void SendTag(const Tag &tag); size_t Play(const void *chunk, size_t size, Error &error); diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx index e3ba7727d..9a389570e 100644 --- a/src/output/plugins/httpd/HttpdOutputPlugin.cxx +++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -499,10 +499,8 @@ httpd_output_pause(AudioOutput *ao) } inline void -HttpdOutput::SendTag(const Tag *tag) +HttpdOutput::SendTag(const Tag &tag) { - assert(tag != nullptr); - if (encoder->plugin.tag != nullptr) { /* embed encoder tags */ @@ -538,7 +536,7 @@ HttpdOutput::SendTag(const Tag *tag) TAG_NUM_OF_ITEM_TYPES }; - metadata = icy_server_metadata_page(*tag, &types[0]); + metadata = icy_server_metadata_page(tag, &types[0]); if (metadata != nullptr) { const ScopeLock protect(mutex); for (auto &client : clients) @@ -548,7 +546,7 @@ HttpdOutput::SendTag(const Tag *tag) } static void -httpd_output_tag(AudioOutput *ao, const Tag *tag) +httpd_output_tag(AudioOutput *ao, const Tag &tag) { HttpdOutput *httpd = HttpdOutput::Cast(ao); diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.hxx b/src/output/plugins/httpd/HttpdOutputPlugin.hxx index df99e2b43..8280d9fb2 100644 --- a/src/output/plugins/httpd/HttpdOutputPlugin.hxx +++ b/src/output/plugins/httpd/HttpdOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx index 146df23d1..fe841ac11 100644 --- a/src/output/plugins/httpd/IcyMetaDataServer.cxx +++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,8 @@ #include "Page.hxx" #include "tag/Tag.hxx" #include "util/FormatString.hxx" - -#include <glib.h> +#include "util/StringUtil.hxx" +#include "util/Macros.hxx" #include <string.h> @@ -57,16 +57,13 @@ icy_server_metadata_header(const char *name, static char * icy_server_metadata_string(const char *stream_title, const char* stream_url) { - gchar *icy_metadata; - guint meta_length; - // The leading n is a placeholder for the length information - icy_metadata = FormatNew("nStreamTitle='%s';" - "StreamUrl='%s';", - stream_title, - stream_url); + char *icy_metadata = FormatNew("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); - meta_length = strlen(icy_metadata); + size_t meta_length = strlen(icy_metadata); meta_length--; // subtract placeholder @@ -85,43 +82,30 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url) Page * icy_server_metadata_page(const Tag &tag, const TagType *types) { - const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; - gint last_item, item; - guint position; - gchar *icy_string; - gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - - // "StreamTitle='';StreamUrl='';" - // = 4081 - 28 - stream_title[0] = '\0'; - - last_item = -1; + const char *tag_items[TAG_NUM_OF_ITEM_TYPES]; + int last_item = -1; while (*types != TAG_NUM_OF_ITEM_TYPES) { - const gchar *tag_item = tag.GetValue(*types++); + const char *tag_item = tag.GetValue(*types++); if (tag_item) tag_items[++last_item] = tag_item; } - position = item = 0; - while (position < sizeof(stream_title) && item <= last_item) { - gint length = 0; - - length = g_strlcpy(stream_title + position, - tag_items[item++], - sizeof(stream_title) - position); + int item = 0; - position += length; + // Length + Metadata - "StreamTitle='';StreamUrl='';" = 4081 - 28 + char stream_title[(1 + 255 - 28) * 16]; + char *p = stream_title, *const end = stream_title + ARRAY_SIZE(stream_title); + stream_title[0] = '\0'; - if (item <= last_item) { - length = g_strlcpy(stream_title + position, - " - ", - sizeof(stream_title) - position); + while (p < end && item <= last_item) { + p = CopyString(p, tag_items[item++], end - p); - position += length; - } + if (item <= last_item) + p = CopyString(p, " - ", end - p); } - icy_string = icy_server_metadata_string(stream_title, ""); + char *icy_string = icy_server_metadata_string(stream_title, ""); if (icy_string == nullptr) return nullptr; diff --git a/src/output/plugins/httpd/IcyMetaDataServer.hxx b/src/output/plugins/httpd/IcyMetaDataServer.hxx index 773b46641..38415e5bd 100644 --- a/src/output/plugins/httpd/IcyMetaDataServer.hxx +++ b/src/output/plugins/httpd/IcyMetaDataServer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/Page.cxx b/src/output/plugins/httpd/Page.cxx index e22134bbc..ff7036645 100644 --- a/src/output/plugins/httpd/Page.cxx +++ b/src/output/plugins/httpd/Page.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/httpd/Page.hxx b/src/output/plugins/httpd/Page.hxx index 95f35d06a..88b7c2d85 100644 --- a/src/output/plugins/httpd/Page.hxx +++ b/src/output/plugins/httpd/Page.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx index 85fd9f2f2..0c39714bb 100644 --- a/src/output/plugins/sles/SlesOutputPlugin.cxx +++ b/src/output/plugins/sles/SlesOutputPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "Play.hxx" #include "AndroidSimpleBufferQueue.hxx" #include "../../OutputAPI.hxx" +#include "../../Wrapper.hxx" #include "util/Macros.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -34,6 +35,8 @@ #include <SLES/OpenSLES_Android.h> class SlesOutput { + friend struct AudioOutputWrapper<SlesOutput>; + static constexpr unsigned N_BUFFERS = 3; static constexpr size_t BUFFER_SIZE = 65536; @@ -455,85 +458,22 @@ sles_output_init(const config_param ¶m, Error &error) return *sles; } -static void -sles_output_finish(AudioOutput *ao) -{ - SlesOutput *sles = (SlesOutput *)ao; - - delete sles; -} - -static bool -sles_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) -{ - SlesOutput &sles = *(SlesOutput *)ao; - - return sles.Open(audio_format, error); -} - -static void -sles_output_close(AudioOutput *ao) -{ - SlesOutput &sles = *(SlesOutput *)ao; - - sles.Close(); -} - -static unsigned -sles_output_delay(AudioOutput *ao) -{ - SlesOutput &sles = *(SlesOutput *)ao; - - return sles.Delay(); -} - -static size_t -sles_output_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) -{ - SlesOutput &sles = *(SlesOutput *)ao; - - return sles.Play(chunk, size, error); -} - -static void -sles_output_drain(AudioOutput *ao) -{ - SlesOutput &sles = *(SlesOutput *)ao; - - sles.Drain(); -} - -static void -sles_output_cancel(AudioOutput *ao) -{ - SlesOutput &sles = *(SlesOutput *)ao; - - sles.Cancel(); -} - -static bool -sles_output_pause(AudioOutput *ao) -{ - SlesOutput &sles = *(SlesOutput *)ao; - - return sles.Pause(); -} +typedef AudioOutputWrapper<SlesOutput> Wrapper; const struct AudioOutputPlugin sles_output_plugin = { "sles", sles_test_default_device, sles_output_init, - sles_output_finish, + &Wrapper::Finish, nullptr, nullptr, - sles_output_open, - sles_output_close, - sles_output_delay, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, nullptr, - sles_output_play, - sles_output_drain, - sles_output_cancel, - sles_output_pause, + &Wrapper::Play, + &Wrapper::Drain, + &Wrapper::Cancel, + &Wrapper::Pause, nullptr, }; diff --git a/src/output/plugins/sles/SlesOutputPlugin.hxx b/src/output/plugins/sles/SlesOutputPlugin.hxx index 5424dec2e..5a7595c24 100644 --- a/src/output/plugins/sles/SlesOutputPlugin.hxx +++ b/src/output/plugins/sles/SlesOutputPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify |