aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/plugins')
-rw-r--r--src/output/plugins/AlsaOutputPlugin.cxx301
-rw-r--r--src/output/plugins/AlsaOutputPlugin.hxx2
-rw-r--r--src/output/plugins/AoOutputPlugin.cxx44
-rw-r--r--src/output/plugins/AoOutputPlugin.hxx2
-rw-r--r--src/output/plugins/FifoOutputPlugin.cxx150
-rw-r--r--src/output/plugins/FifoOutputPlugin.hxx2
-rw-r--r--src/output/plugins/HaikuOutputPlugin.cxx532
-rw-r--r--src/output/plugins/HaikuOutputPlugin.hxx34
-rw-r--r--src/output/plugins/JackOutputPlugin.cxx728
-rw-r--r--src/output/plugins/JackOutputPlugin.hxx2
-rw-r--r--src/output/plugins/NullOutputPlugin.cxx133
-rw-r--r--src/output/plugins/NullOutputPlugin.hxx2
-rw-r--r--src/output/plugins/OSXOutputPlugin.cxx28
-rw-r--r--src/output/plugins/OSXOutputPlugin.hxx2
-rw-r--r--src/output/plugins/OpenALOutputPlugin.cxx245
-rw-r--r--src/output/plugins/OpenALOutputPlugin.hxx2
-rw-r--r--src/output/plugins/OssOutputPlugin.cxx251
-rw-r--r--src/output/plugins/OssOutputPlugin.hxx2
-rw-r--r--src/output/plugins/PipeOutputPlugin.cxx100
-rw-r--r--src/output/plugins/PipeOutputPlugin.hxx2
-rw-r--r--src/output/plugins/PulseOutputPlugin.cxx882
-rw-r--r--src/output/plugins/PulseOutputPlugin.hxx4
-rw-r--r--src/output/plugins/RecorderOutputPlugin.cxx352
-rw-r--r--src/output/plugins/RecorderOutputPlugin.hxx2
-rw-r--r--src/output/plugins/RoarOutputPlugin.cxx80
-rw-r--r--src/output/plugins/RoarOutputPlugin.hxx2
-rw-r--r--src/output/plugins/ShoutOutputPlugin.cxx71
-rw-r--r--src/output/plugins/ShoutOutputPlugin.hxx2
-rw-r--r--src/output/plugins/SolarisOutputPlugin.cxx12
-rw-r--r--src/output/plugins/SolarisOutputPlugin.hxx2
-rw-r--r--src/output/plugins/WinmmOutputPlugin.cxx52
-rw-r--r--src/output/plugins/WinmmOutputPlugin.hxx2
-rw-r--r--src/output/plugins/httpd/HttpdClient.cxx4
-rw-r--r--src/output/plugins/httpd/HttpdClient.hxx10
-rw-r--r--src/output/plugins/httpd/HttpdInternal.hxx33
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.cxx80
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.hxx2
-rw-r--r--src/output/plugins/httpd/IcyMetaDataServer.cxx58
-rw-r--r--src/output/plugins/httpd/IcyMetaDataServer.hxx2
-rw-r--r--src/output/plugins/httpd/Page.cxx2
-rw-r--r--src/output/plugins/httpd/Page.hxx2
-rw-r--r--src/output/plugins/sles/SlesOutputPlugin.cxx108
-rw-r--r--src/output/plugins/sles/SlesOutputPlugin.hxx2
43 files changed, 2467 insertions, 1863 deletions
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index 28c374a00..8a7bb9643 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,92 +132,108 @@ struct AlsaOutput {
mode(0), writei(snd_pcm_writei) {
}
- bool Configure(const config_param &param, Error &error);
+ ~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 ConfigBlock &block, Error &error);
+ static AlsaOutput *Create(const ConfigBlock &block, 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 &param, Error &error)
+AlsaOutput::Configure(const ConfigBlock &block, Error &error)
{
- if (!base.Configure(param, error))
+ if (!base.Configure(block, error))
return false;
- device = param.GetBlockValue("device", "");
+ device = block.GetBlockValue("device", "");
- use_mmap = param.GetBlockValue("use_mmap", false);
+ use_mmap = block.GetBlockValue("use_mmap", false);
- dop = param.GetBlockValue("dop", false) ||
+ dop = block.GetBlockValue("dop", false) ||
/* legacy name from MPD 0.18 and older: */
- param.GetBlockValue("dsd_usb", false);
+ block.GetBlockValue("dsd_usb", false);
- buffer_time = param.GetBlockValue("buffer_time",
+ buffer_time = block.GetBlockValue("buffer_time",
MPD_ALSA_BUFFER_TIME_US);
- period_time = param.GetBlockValue("period_time", 0u);
+ period_time = block.GetBlockValue("period_time", 0u);
#ifdef SND_PCM_NO_AUTO_RESAMPLE
- if (!param.GetBlockValue("auto_resample", true))
+ if (!block.GetBlockValue("auto_resample", true))
mode |= SND_PCM_NO_AUTO_RESAMPLE;
#endif
#ifdef SND_PCM_NO_AUTO_CHANNELS
- if (!param.GetBlockValue("auto_channels", true))
+ if (!block.GetBlockValue("auto_channels", true))
mode |= SND_PCM_NO_AUTO_CHANNELS;
#endif
#ifdef SND_PCM_NO_AUTO_FORMAT
- if (!param.GetBlockValue("auto_format", true))
+ if (!block.GetBlockValue("auto_format", true))
mode |= SND_PCM_NO_AUTO_FORMAT;
#endif
return true;
}
-static AudioOutput *
-alsa_init(const config_param &param, Error &error)
+inline AlsaOutput *
+AlsaOutput::Create(const ConfigBlock &block, Error &error)
{
AlsaOutput *ad = new AlsaOutput();
- if (!ad->Configure(param, error)) {
+ if (!ad->Configure(block, error)) {
delete ad;
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..3c0cf74a4 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>
@@ -45,11 +46,11 @@ struct AoOutput {
AoOutput()
:base(ao_output_plugin) {}
- bool Initialize(const config_param &param, Error &error) {
- return base.Configure(param, error);
+ bool Initialize(const ConfigBlock &block, Error &error) {
+ return base.Configure(block, error);
}
- bool Configure(const config_param &param, Error &error);
+ bool Configure(const ConfigBlock &block, Error &error);
};
static constexpr Domain ao_output_domain("ao_output");
@@ -89,20 +90,20 @@ ao_output_error(Error &error_r)
}
inline bool
-AoOutput::Configure(const config_param &param, Error &error)
+AoOutput::Configure(const ConfigBlock &block, Error &error)
{
const char *value;
options = nullptr;
- write_size = param.GetBlockValue("write_size", 1024u);
+ write_size = block.GetBlockValue("write_size", 1024u);
if (ao_output_ref == 0) {
ao_initialize();
}
ao_output_ref++;
- value = param.GetBlockValue("driver", "default");
+ value = block.GetBlockValue("driver", "default");
if (0 == strcmp(value, "default"))
driver = ao_default_driver_id();
else
@@ -122,45 +123,38 @@ AoOutput::Configure(const config_param &param, Error &error)
}
FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n",
- ai->short_name, param.GetBlockValue("name", nullptr));
+ ai->short_name, block.GetBlockValue("name", nullptr));
- value = param.GetBlockValue("options", nullptr);
+ value = block.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;
}
static AudioOutput *
-ao_output_init(const config_param &param, Error &error)
+ao_output_init(const ConfigBlock &block, Error &error)
{
AoOutput *ad = new AoOutput();
- if (!ad->Initialize(param, error)) {
+ if (!ad->Initialize(block, error)) {
delete ad;
return nullptr;
}
- if (!ad->Configure(param, error)) {
+ if (!ad->Configure(block, error)) {
delete ad;
return nullptr;
}
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..d4019df53 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
@@ -21,9 +21,11 @@
#include "FifoOutputPlugin.hxx"
#include "config/ConfigError.hxx"
#include "../OutputAPI.hxx"
+#include "../Wrapper.hxx"
#include "../Timer.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
+#include "fs/FileInfo.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -33,9 +35,9 @@
#include <errno.h>
#include <unistd.h>
-#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
+class FifoOutput {
+ friend struct AudioOutputWrapper<FifoOutput>;
-struct FifoOutput {
AudioOutput base;
AllocatedPath path;
@@ -46,21 +48,35 @@ struct FifoOutput {
bool created;
Timer *timer;
+public:
FifoOutput()
:base(fifo_output_plugin),
path(AllocatedPath::Null()), input(-1), output(-1),
created(false) {}
- bool Initialize(const config_param &param, Error &error) {
- return base.Configure(param, error);
+ ~FifoOutput() {
+ CloseFifo();
}
+ bool Initialize(const ConfigBlock &block, Error &error) {
+ return base.Configure(block, error);
+ }
+
+ static FifoOutput *Create(const ConfigBlock &block, Error &error);
+
bool Create(Error &error);
bool Check(Error &error);
void Delete();
- bool Open(Error &error);
+ bool OpenFifo(Error &error);
+ void CloseFifo();
+
+ bool Open(AudioFormat &audio_format, Error &error);
void Close();
+
+ unsigned Delay() const;
+ size_t Play(const void *chunk, size_t size, Error &error);
+ void Cancel();
};
static constexpr Domain fifo_output_domain("fifo_output");
@@ -82,7 +98,7 @@ FifoOutput::Delete()
}
void
-FifoOutput::Close()
+FifoOutput::CloseFifo()
{
if (input >= 0) {
close(input);
@@ -94,8 +110,8 @@ FifoOutput::Close()
output = -1;
}
- struct stat st;
- if (created && StatFile(path, st))
+ FileInfo fi;
+ if (created && GetFileInfo(path, fi))
Delete();
}
@@ -138,7 +154,7 @@ FifoOutput::Check(Error &error)
}
inline bool
-FifoOutput::Open(Error &error)
+FifoOutput::OpenFifo(Error &error)
{
if (!Check(error))
return false;
@@ -147,7 +163,7 @@ FifoOutput::Open(Error &error)
if (input < 0) {
error.FormatErrno("Could not open FIFO \"%s\" for reading",
path_utf8.c_str());
- Close();
+ CloseFifo();
return false;
}
@@ -155,25 +171,19 @@ FifoOutput::Open(Error &error)
if (output < 0) {
error.FormatErrno("Could not open FIFO \"%s\" for writing",
path_utf8.c_str());
- Close();
+ CloseFifo();
return false;
}
return true;
}
-static bool
-fifo_open(FifoOutput *fd, Error &error)
-{
- return fd->Open(error);
-}
-
-static AudioOutput *
-fifo_output_init(const config_param &param, Error &error)
+inline FifoOutput *
+FifoOutput::Create(const ConfigBlock &block, Error &error)
{
FifoOutput *fd = new FifoOutput();
- fd->path = param.GetBlockPath("path", error);
+ fd->path = block.GetBlockPath("path", error);
if (fd->path.IsNull()) {
delete fd;
@@ -185,89 +195,67 @@ fifo_output_init(const config_param &param, Error &error)
fd->path_utf8 = fd->path.ToUTF8();
- if (!fd->Initialize(param, error)) {
+ if (!fd->Initialize(block, error)) {
delete fd;
return nullptr;
}
- if (!fifo_open(fd, error)) {
+ if (!fd->OpenFifo(error)) {
delete fd;
return nullptr;
}
- return &fd->base;
-}
-
-static void
-fifo_output_finish(AudioOutput *ao)
-{
- FifoOutput *fd = (FifoOutput *)ao;
-
- fd->Close();
- delete fd;
+ return fd;
}
-static bool
-fifo_output_open(AudioOutput *ao, AudioFormat &audio_format,
- gcc_unused Error &error)
+bool
+FifoOutput::Open(AudioFormat &audio_format, gcc_unused Error &error)
{
- FifoOutput *fd = (FifoOutput *)ao;
-
- fd->timer = new Timer(audio_format);
-
+ timer = new Timer(audio_format);
return true;
}
-static void
-fifo_output_close(AudioOutput *ao)
+void
+FifoOutput::Close()
{
- FifoOutput *fd = (FifoOutput *)ao;
-
- delete fd->timer;
+ delete timer;
}
-static void
-fifo_output_cancel(AudioOutput *ao)
+inline void
+FifoOutput::Cancel()
{
- FifoOutput *fd = (FifoOutput *)ao;
- char buf[FIFO_BUFFER_SIZE];
- int bytes = 1;
-
- fd->timer->Reset();
+ timer->Reset();
- while (bytes > 0 && errno != EINTR)
- bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
+ ssize_t bytes;
+ do {
+ char buffer[16384];
+ bytes = read(input, buffer, sizeof(buffer));
+ } while (bytes > 0 && errno != EINTR);
if (bytes < 0 && errno != EAGAIN) {
FormatErrno(fifo_output_domain,
"Flush of FIFO \"%s\" failed",
- fd->path_utf8.c_str());
+ path_utf8.c_str());
}
}
-static unsigned
-fifo_output_delay(AudioOutput *ao)
+inline unsigned
+FifoOutput::Delay() const
{
- FifoOutput *fd = (FifoOutput *)ao;
-
- return fd->timer->IsStarted()
- ? fd->timer->GetDelay()
+ return timer->IsStarted()
+ ? timer->GetDelay()
: 0;
}
-static size_t
-fifo_output_play(AudioOutput *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+FifoOutput::Play(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);
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
while (true) {
- bytes = write(fd->output, chunk, size);
+ ssize_t bytes = write(output, chunk, size);
if (bytes > 0)
return (size_t)bytes;
@@ -275,33 +263,35 @@ fifo_output_play(AudioOutput *ao, const void *chunk, size_t size,
switch (errno) {
case EAGAIN:
/* The pipe is full, so empty it */
- fifo_output_cancel(&fd->base);
+ Cancel();
continue;
case EINTR:
continue;
}
error.FormatErrno("Failed to write to FIFO %s",
- fd->path_utf8.c_str());
+ path_utf8.c_str());
return 0;
}
}
}
+typedef AudioOutputWrapper<FifoOutput> Wrapper;
+
const struct AudioOutputPlugin fifo_output_plugin = {
"fifo",
nullptr,
- fifo_output_init,
- fifo_output_finish,
+ &Wrapper::Init,
+ &Wrapper::Finish,
nullptr,
nullptr,
- fifo_output_open,
- fifo_output_close,
- fifo_output_delay,
+ &Wrapper::Open,
+ &Wrapper::Close,
+ &Wrapper::Delay,
nullptr,
- fifo_output_play,
+ &Wrapper::Play,
nullptr,
- fifo_output_cancel,
+ &Wrapper::Cancel,
nullptr,
nullptr,
};
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/HaikuOutputPlugin.cxx b/src/output/plugins/HaikuOutputPlugin.cxx
new file mode 100644
index 000000000..e235d595a
--- /dev/null
+++ b/src/output/plugins/HaikuOutputPlugin.cxx
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2003-2015 The Music Player Daemon Project
+ * http://www.musicpd.org
+ * Copyright (C) 2014-2015 François 'mmu_man' Revol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HaikuOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "../Wrapper.hxx"
+#include "mixer/MixerList.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <AppFileInfo.h>
+#include <Application.h>
+#include <Bitmap.h>
+#include <IconUtils.h>
+#include <MediaDefs.h>
+#include <MediaRoster.h>
+#include <Notification.h>
+#include <OS.h>
+#include <Resources.h>
+#include <StringList.h>
+#include <SoundPlayer.h>
+
+#include <string.h>
+
+#define UTF8_PLAY "\xE2\x96\xB6"
+
+class HaikuOutput {
+ friend struct AudioOutputWrapper<HaikuOutput>;
+ friend int haiku_output_get_volume(HaikuOutput &haiku);
+ friend bool haiku_output_set_volume(HaikuOutput &haiku, unsigned volume);
+
+ AudioOutput base;
+
+ size_t write_size;
+
+ media_raw_audio_format* format;
+ BSoundPlayer* sound_player;
+
+ sem_id new_buffer;
+ sem_id buffer_done;
+
+ uint8* buffer;
+ size_t buffer_size;
+ size_t buffer_filled;
+
+ unsigned buffer_delay;
+
+public:
+ HaikuOutput()
+ :base(haiku_output_plugin) {}
+ ~HaikuOutput();
+
+ bool Initialize(const ConfigBlock &block, Error &error) {
+ return base.Configure(block, error);
+ }
+
+ static HaikuOutput *Create(const ConfigBlock &block, Error &error);
+
+ bool Open(AudioFormat &audio_format, Error &error);
+
+ void Close() {
+ DoClose();
+ }
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+ void Cancel();
+
+ bool Configure(const ConfigBlock &block, Error &error);
+
+ size_t Delay();
+
+ void FillBuffer(void* _buffer, size_t size,
+ gcc_unused const media_raw_audio_format& _format);
+
+ void SendTag(const Tag &tag);
+
+private:
+
+ void DoClose();
+};
+
+static constexpr Domain haiku_output_domain("haiku_output");
+
+static void
+haiku_output_error(Error &error_r, status_t err)
+{
+ const char *error = strerror(err);
+ error_r.Set(haiku_output_domain, err, error);
+}
+
+static void
+initialize_application()
+{
+ // required to send the notification with a bitmap
+ // TODO: actually Run() it and handle B_QUIT_REQUESTED
+ // TODO: use some locking?
+ if (be_app == NULL) {
+ FormatDebug(haiku_output_domain, "creating be_app\n");
+ new BApplication("application/x-vnd.MusicPD");
+ }
+}
+
+static void
+finalize_application()
+{
+ // TODO: use some locking?
+ delete be_app;
+ be_app = NULL;
+ FormatDebug(haiku_output_domain, "deleting be_app\n");
+}
+
+inline bool
+HaikuOutput::Configure(const ConfigBlock &block, Error &error)
+{
+ /* XXX: by default we should let the MediaKit propose the buffer size */
+ write_size = block.GetBlockValue("write_size", 4096u);
+
+ format = (media_raw_audio_format*)malloc(
+ sizeof(media_raw_audio_format));
+ if (format == nullptr) {
+ haiku_output_error(error, B_NO_MEMORY);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+haiku_test_default_device(void)
+{
+ BSoundPlayer testPlayer;
+ return testPlayer.InitCheck() == B_OK;
+
+}
+
+inline HaikuOutput *
+HaikuOutput::Create(const ConfigBlock &block, Error &error)
+{
+ initialize_application();
+
+ HaikuOutput *ad = new HaikuOutput();
+
+ if (!ad->Initialize(block, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ if (!ad->Configure(block, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ return ad;
+}
+
+void
+HaikuOutput::DoClose()
+{
+ sound_player->SetHasData(false);
+ delete_sem(new_buffer);
+ delete_sem(buffer_done);
+ sound_player->Stop();
+ delete sound_player;
+ sound_player = nullptr;
+}
+
+
+
+HaikuOutput::~HaikuOutput()
+{
+ free(format);
+ delete_sem(new_buffer);
+ delete_sem(buffer_done);
+
+ finalize_application();
+}
+
+static void
+fill_buffer(void* cookie, void* buffer, size_t size,
+ const media_raw_audio_format& format)
+{
+ HaikuOutput *ad = (HaikuOutput *)cookie;
+ ad->FillBuffer(buffer, size, format);
+}
+
+
+void
+HaikuOutput::FillBuffer(void* _buffer, size_t size,
+ gcc_unused const media_raw_audio_format& _format)
+{
+
+ buffer = (uint8*)_buffer;
+ buffer_size = size;
+ buffer_filled = 0;
+ bigtime_t start = system_time();
+ release_sem(new_buffer);
+ acquire_sem(buffer_done);
+ bigtime_t w = system_time() - start;
+
+ if (w > 5000LL) {
+ FormatDebug(haiku_output_domain,
+ "haiku:fill_buffer waited %Ldus\n", w);
+ }
+
+ if (buffer_filled < buffer_size) {
+ memset(buffer + buffer_filled, 0,
+ buffer_size - buffer_filled);
+ FormatDebug(haiku_output_domain,
+ "haiku:fill_buffer filled %d size %d clearing remainder\n",
+ (int)buffer_filled, (int)buffer_size);
+
+ }
+}
+
+inline bool
+HaikuOutput::Open(AudioFormat &audio_format, Error &error)
+{
+ status_t err;
+ *format = media_multi_audio_format::wildcard;
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ format->format = media_raw_audio_format::B_AUDIO_CHAR;
+ break;
+
+ case SampleFormat::S16:
+ format->format = media_raw_audio_format::B_AUDIO_SHORT;
+ break;
+
+ case SampleFormat::S32:
+ format->format = media_raw_audio_format::B_AUDIO_INT;
+ break;
+
+ case SampleFormat::FLOAT:
+ format->format = media_raw_audio_format::B_AUDIO_FLOAT;
+ break;
+
+ default:
+ /* fall back to float */
+ audio_format.format = SampleFormat::FLOAT;
+ format->format = media_raw_audio_format::B_AUDIO_FLOAT;
+ break;
+ }
+
+ format->frame_rate = audio_format.sample_rate;
+ format->byte_order = B_MEDIA_HOST_ENDIAN;
+ format->channel_count = audio_format.channels;
+
+ buffer_size = 0;
+
+ if (write_size)
+ format->buffer_size = write_size;
+ else
+ format->buffer_size = BMediaRoster::Roster()->AudioBufferSizeFor(
+ format->channel_count, format->format,
+ format->frame_rate, B_UNKNOWN_BUS) * 2;
+
+ FormatDebug(haiku_output_domain,
+ "using haiku driver ad: bs: %d ws: %d "
+ "channels %d rate %f fmt %08lx bs %d\n",
+ (int)buffer_size, (int)write_size,
+ (int)format->channel_count, format->frame_rate,
+ format->format, (int)format->buffer_size);
+
+ sound_player = new BSoundPlayer(format, "MPD Output",
+ fill_buffer, NULL, this);
+
+ err = sound_player->InitCheck();
+ if (err != B_OK) {
+ delete sound_player;
+ sound_player = NULL;
+ haiku_output_error(error, err);
+ return false;
+ }
+
+ // calculate the allowable delay for the buffer (ms)
+ buffer_delay = format->buffer_size;
+ buffer_delay /= (format->format &
+ media_raw_audio_format::B_AUDIO_SIZE_MASK);
+ buffer_delay /= format->channel_count;
+ buffer_delay *= 1000 / format->frame_rate;
+ // half of the total buffer play time
+ buffer_delay /= 2;
+ FormatDebug(haiku_output_domain,
+ "buffer delay: %d ms\n", buffer_delay);
+
+ new_buffer = create_sem(0, "New buffer request");
+ buffer_done = create_sem(0, "Buffer done");
+
+ sound_player->SetVolume(1.0);
+ sound_player->Start();
+ sound_player->SetHasData(false);
+
+ return true;
+}
+
+inline size_t
+HaikuOutput::Play(const void *chunk, size_t size, gcc_unused Error &error)
+{
+ BSoundPlayer* const soundPlayer = sound_player;
+ const uint8 *data = (const uint8 *)chunk;
+
+ if (size == 0) {
+ soundPlayer->SetHasData(false);
+ return 0;
+ }
+
+ if (!soundPlayer->HasData())
+ soundPlayer->SetHasData(true);
+ acquire_sem(new_buffer);
+
+ size_t bytesLeft = size;
+ while (bytesLeft > 0) {
+ if (buffer_filled == buffer_size) {
+ // Request another buffer from BSoundPlayer
+ release_sem(buffer_done);
+ acquire_sem(new_buffer);
+ }
+
+ const size_t copyBytes = std::min(bytesLeft, buffer_size
+ - buffer_filled);
+ memcpy(buffer + buffer_filled, data,
+ copyBytes);
+ buffer_filled += copyBytes;
+ data += copyBytes;
+ bytesLeft -= copyBytes;
+ }
+
+
+ if (buffer_filled < buffer_size) {
+ // Continue filling this buffer the next time this function is called
+ release_sem(new_buffer);
+ } else {
+ // Buffer is full
+ release_sem(buffer_done);
+ //soundPlayer->SetHasData(false);
+ }
+
+ return size;
+}
+
+inline size_t
+HaikuOutput::Delay()
+{
+ unsigned delay = buffer_filled ? 0 : buffer_delay;
+
+ //FormatDebug(haiku_output_domain,
+ // "delay=%d\n", delay / 2);
+ // XXX: doesn't work
+ //return (delay / 2) ? 1 : 0;
+ (void)delay;
+
+ return 0;
+}
+
+inline void
+HaikuOutput::SendTag(const Tag &tag)
+{
+ status_t err;
+
+ /* lazily initialized */
+ static BBitmap *icon = NULL;
+
+ if (icon == NULL) {
+ BAppFileInfo info;
+ BResources resources;
+ err = resources.SetToImage((const void *)&HaikuOutput::SendTag);
+ BFile file(resources.File());
+ err = info.SetTo(&file);
+ icon = new BBitmap(BRect(0, 0, (float)B_LARGE_ICON - 1,
+ (float)B_LARGE_ICON - 1), B_BITMAP_NO_SERVER_LINK, B_RGBA32);
+ err = info.GetIcon(icon, B_LARGE_ICON);
+ if (err != B_OK) {
+ delete icon;
+ icon = NULL;
+ }
+ }
+
+ BNotification notification(B_INFORMATION_NOTIFICATION);
+
+ BString messageId("mpd_");
+ messageId << find_thread(NULL);
+ notification.SetMessageID(messageId);
+
+ notification.SetGroup("Music Player Daemon");
+
+ char timebuf[16];
+ unsigned seconds = 0;
+ if (!tag.duration.IsNegative()) {
+ seconds = tag.duration.ToS();
+ snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+ seconds / 3600, (seconds % 3600) / 60, seconds % 60);
+ }
+
+ BString artist;
+ BString album;
+ BString title;
+ BString track;
+ BString name;
+
+ for (const auto &item : tag)
+ {
+ switch (item.type) {
+ case TAG_ARTIST:
+ case TAG_ALBUM_ARTIST:
+ if (artist.Length() == 0)
+ artist << item.value;
+ break;
+ case TAG_ALBUM:
+ if (album.Length() == 0)
+ album << item.value;
+ break;
+ case TAG_TITLE:
+ if (title.Length() == 0)
+ title << item.value;
+ break;
+ case TAG_TRACK:
+ if (track.Length() == 0)
+ track << item.value;
+ break;
+ case TAG_NAME:
+ if (name.Length() == 0)
+ name << item.value;
+ break;
+ case TAG_GENRE:
+ case TAG_DATE:
+ case TAG_PERFORMER:
+ case TAG_COMMENT:
+ case TAG_DISC:
+ case TAG_COMPOSER:
+ case TAG_MUSICBRAINZ_ARTISTID:
+ case TAG_MUSICBRAINZ_ALBUMID:
+ case TAG_MUSICBRAINZ_ALBUMARTISTID:
+ case TAG_MUSICBRAINZ_TRACKID:
+ default:
+ FormatDebug(haiku_output_domain,
+ "tag item: type %d value '%s'\n", item.type, item.value);
+ break;
+ }
+ }
+
+ notification.SetTitle(UTF8_PLAY " Now Playing:");
+
+ BStringList content;
+ if (name.Length())
+ content.Add(name);
+ if (artist.Length())
+ content.Add(artist);
+ if (album.Length())
+ content.Add(album);
+ if (track.Length())
+ content.Add(track);
+ if (title.Length())
+ content.Add(title);
+
+ if (content.CountStrings() == 0)
+ content.Add("(Unknown)");
+
+ BString full = content.Join(" " B_UTF8_BULLET " ");
+
+ if (seconds > 0)
+ full << " (" << timebuf << ")";
+
+ notification.SetContent(full);
+
+ err = notification.SetIcon(icon);
+
+ notification.Send();
+}
+
+int
+haiku_output_get_volume(HaikuOutput &haiku)
+{
+ BSoundPlayer* const soundPlayer = haiku.sound_player;
+
+ if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK)
+ return 0;
+
+ return (int)(soundPlayer->Volume() * 100 + 0.5);
+}
+
+bool
+haiku_output_set_volume(HaikuOutput &haiku, unsigned volume)
+{
+ BSoundPlayer* const soundPlayer = haiku.sound_player;
+
+ if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK)
+ return false;
+
+ soundPlayer->SetVolume((float)volume / 100);
+ return true;
+}
+
+typedef AudioOutputWrapper<HaikuOutput> Wrapper;
+
+const struct AudioOutputPlugin haiku_output_plugin = {
+ "haiku",
+ haiku_test_default_device,
+ &Wrapper::Init,
+ &Wrapper::Finish,
+ nullptr,
+ nullptr,
+ &Wrapper::Open,
+ &Wrapper::Close,
+ &Wrapper::Delay,
+ &Wrapper::SendTag,
+ &Wrapper::Play,
+ nullptr,
+ nullptr,
+ nullptr,
+
+ &haiku_mixer_plugin,
+};
diff --git a/src/output/plugins/HaikuOutputPlugin.hxx b/src/output/plugins/HaikuOutputPlugin.hxx
new file mode 100644
index 000000000..fb85d4b83
--- /dev/null
+++ b/src/output/plugins/HaikuOutputPlugin.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2015 The Music Player Daemon Project
+ * http://www.musicpd.org
+ * Copyright (C) 2014-2015 François 'mmu_man' Revol
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_HAIKU_OUTPUT_PLUGIN_HXX
+#define MPD_HAIKU_OUTPUT_PLUGIN_HXX
+
+class HaikuOutput;
+
+extern const struct AudioOutputPlugin haiku_output_plugin;
+
+int
+haiku_output_get_volume(HaikuOutput &haiku);
+
+bool
+haiku_output_set_volume(HaikuOutput &haiku, unsigned volume);
+
+#endif
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index e1dad7893..23843ab5e 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 &param, Error &error_r) {
- return base.Configure(param, error_r);
+ bool Configure(const ConfigBlock &block, 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)
{
- JackOutput *jd = (JackOutput *) arg;
+ 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)
+{
+ 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);
+ MultiWriteSilence({ports, n_channels}, nframes);
- for (jack_nframes_t f = 0; f < nframes; ++f)
- out[f] = 0.0;
- }
-
- 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);
-
- jd->shutdown = false;
+ 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,243 +375,221 @@ parse_port_list(const char *source, char **dest, Error &error)
return n;
}
-static AudioOutput *
-mpd_jack_init(const config_param &param, Error &error)
+bool
+JackOutput::Configure(const ConfigBlock &block, Error &error)
{
- JackOutput *jd = new JackOutput();
-
- if (!jd->Initialize(param, error)) {
- delete jd;
- return nullptr;
- }
-
- const char *value;
+ if (!base.Configure(block, 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 = block.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 = block.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);
+ if (!block.GetBlockValue("autostart", false))
+ 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 = block.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 */
- value = param.GetBlockValue("destination_ports", nullptr);
+ value = block.GetBlockValue("destination_ports", nullptr);
if (value == nullptr) {
/* compatibility with MPD < 0.16 */
- value = param.GetBlockValue("ports", nullptr);
+ value = block.GetBlockValue("ports", nullptr);
if (value != nullptr)
FormatWarning(jack_output_domain,
"deprecated option 'ports' in line %d",
- param.line);
+ block.line);
}
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,
- param.line);
-
- jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
+ num_source_ports, num_destination_ports,
+ block.line);
- jack_set_error_function(mpd_jack_error);
+ ringbuffer_size = block.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;
+ if (client != nullptr)
+ Disconnect();
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- jd->ringbuffer[i] = nullptr;
-
- 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 ConfigBlock &block, 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(block, 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);
-
- for (unsigned i = 0; i < num_destination_ports; ++i) {
- int ret;
+ assert(num_dports <= num_source_ports);
- 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..e1731f0fe 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
@@ -20,119 +20,94 @@
#include "config.h"
#include "NullOutputPlugin.hxx"
#include "../OutputAPI.hxx"
+#include "../Wrapper.hxx"
#include "../Timer.hxx"
-struct NullOutput {
+class NullOutput {
+ friend struct AudioOutputWrapper<NullOutput>;
+
AudioOutput base;
bool sync;
Timer *timer;
+public:
NullOutput()
:base(null_output_plugin) {}
- bool Initialize(const config_param &param, Error &error) {
- return base.Configure(param, error);
- }
-};
-
-static AudioOutput *
-null_init(const config_param &param, Error &error)
-{
- NullOutput *nd = new NullOutput();
-
- if (!nd->Initialize(param, error)) {
- delete nd;
- return nullptr;
+ bool Initialize(const ConfigBlock &block, Error &error) {
+ return base.Configure(block, error);
}
- nd->sync = param.GetBlockValue("sync", true);
+ static NullOutput *Create(const ConfigBlock &block, Error &error);
- return &nd->base;
-}
+ bool Open(AudioFormat &audio_format, gcc_unused Error &error) {
+ if (sync)
+ timer = new Timer(audio_format);
-static void
-null_finish(AudioOutput *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- delete nd;
-}
-
-static bool
-null_open(AudioOutput *ao, AudioFormat &audio_format,
- gcc_unused Error &error)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- if (nd->sync)
- nd->timer = new Timer(audio_format);
+ return true;
+ }
- return true;
-}
+ void Close() {
+ if (sync)
+ delete timer;
+ }
-static void
-null_close(AudioOutput *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
+ unsigned Delay() const {
+ return sync && timer->IsStarted()
+ ? timer->GetDelay()
+ : 0;
+ }
- if (nd->sync)
- delete nd->timer;
-}
+ size_t Play(gcc_unused const void *chunk, size_t size,
+ gcc_unused Error &error) {
+ if (sync) {
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
+ }
-static unsigned
-null_delay(AudioOutput *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
+ return size;
+ }
- return nd->sync && nd->timer->IsStarted()
- ? nd->timer->GetDelay()
- : 0;
-}
+ void Cancel() {
+ if (sync)
+ timer->Reset();
+ }
+};
-static size_t
-null_play(AudioOutput *ao, gcc_unused const void *chunk, size_t size,
- gcc_unused Error &error)
+inline NullOutput *
+NullOutput::Create(const ConfigBlock &block, Error &error)
{
- NullOutput *nd = (NullOutput *)ao;
- Timer *timer = nd->timer;
+ NullOutput *nd = new NullOutput();
- if (!nd->sync)
- return size;
+ if (!nd->Initialize(block, error)) {
+ delete nd;
+ return nullptr;
+ }
- if (!timer->IsStarted())
- timer->Start();
- timer->Add(size);
+ nd->sync = block.GetBlockValue("sync", true);
- return size;
+ return nd;
}
-static void
-null_cancel(AudioOutput *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- if (!nd->sync)
- return;
-
- nd->timer->Reset();
-}
+typedef AudioOutputWrapper<NullOutput> Wrapper;
const struct AudioOutputPlugin null_output_plugin = {
"null",
nullptr,
- null_init,
- null_finish,
+ &Wrapper::Init,
+ &Wrapper::Finish,
nullptr,
nullptr,
- null_open,
- null_close,
- null_delay,
+ &Wrapper::Open,
+ &Wrapper::Close,
+ &Wrapper::Delay,
nullptr,
- null_play,
+ &Wrapper::Play,
nullptr,
- null_cancel,
+ &Wrapper::Cancel,
nullptr,
nullptr,
};
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..16c042ba3 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
@@ -61,17 +61,17 @@ osx_output_test_default_device(void)
}
static void
-osx_output_configure(OSXOutput *oo, const config_param &param)
+osx_output_configure(OSXOutput *oo, const ConfigBlock &block)
{
- const char *device = param.GetBlockValue("device");
+ const char *device = block.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;
@@ -81,15 +81,15 @@ osx_output_configure(OSXOutput *oo, const config_param &param)
}
static AudioOutput *
-osx_output_init(const config_param &param, Error &error)
+osx_output_init(const ConfigBlock &block, Error &error)
{
OSXOutput *oo = new OSXOutput();
- if (!oo->base.Configure(param, error)) {
+ if (!oo->base.Configure(block, error)) {
delete oo;
- return NULL;
+ return nullptr;
}
- osx_output_configure(oo, param);
+ osx_output_configure(oo, block);
return &oo->base;
}
@@ -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..eb55c6e9b 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 &param, Error &error_r) {
- return base.Configure(param, error_r);
+ bool Configure(const ConfigBlock &block, Error &error);
+
+ static OpenALOutput *Create(const ConfigBlock &block, 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 &param, Error &error)
+inline bool
+OpenALOutput::Configure(const ConfigBlock &block, 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(block, error))
+ return false;
- od->device_name = device_name;
+ device_name = block.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 ConfigBlock &block, Error &error)
{
- OpenALOutput *od = (OpenALOutput *)ao;
+ OpenALOutput *oo = new OpenALOutput();
+
+ if (!oo->Configure(block, 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..7f75f4e31 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
@@ -20,6 +20,7 @@
#include "config.h"
#include "OssOutputPlugin.hxx"
#include "../OutputAPI.hxx"
+#include "../Wrapper.hxx"
#include "mixer/MixerList.hxx"
#include "system/fd_util.h"
#include "util/ConstBuffer.hxx"
@@ -57,7 +58,9 @@
#include "util/Manual.hxx"
#endif
-struct OssOutput {
+class OssOutput {
+ friend struct AudioOutputWrapper<OssOutput>;
+
AudioOutput base;
#ifdef AFMT_S24_PACKED
@@ -79,13 +82,49 @@ struct OssOutput {
*/
int oss_format;
- OssOutput()
+public:
+ OssOutput(const char *_device=nullptr)
:base(oss_output_plugin),
- fd(-1), device(nullptr) {}
+ fd(-1), device(_device) {}
- bool Initialize(const config_param &param, Error &error_r) {
- return base.Configure(param, error_r);
+ bool Initialize(const ConfigBlock &block, Error &error_r) {
+ return base.Configure(block, error_r);
}
+
+ static OssOutput *Create(const ConfigBlock &block, Error &error);
+
+#ifdef AFMT_S24_PACKED
+ bool Enable(gcc_unused Error &error) {
+ pcm_export.Construct();
+ return true;
+ }
+
+ void Disable() {
+ pcm_export.Destruct();
+ }
+#endif
+
+ bool Open(AudioFormat &audio_format, Error &error);
+
+ void Close() {
+ DoClose();
+ }
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+ void Cancel();
+
+private:
+ /**
+ * Sets up the OSS device which was opened before.
+ */
+ bool Setup(AudioFormat &audio_format, Error &error);
+
+ /**
+ * Reopen the device with the saved audio_format, without any probing.
+ */
+ bool Reopen(Error &error);
+
+ void DoClose();
};
static constexpr Domain oss_output_domain("oss_output");
@@ -124,7 +163,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)
@@ -147,24 +186,23 @@ oss_output_test_default_device(void)
return false;
}
-static AudioOutput *
+static OssOutput *
oss_open_default(Error &error)
{
int err[ARRAY_SIZE(default_devices)];
enum oss_stat ret[ARRAY_SIZE(default_devices)];
- const config_param empty;
+ const ConfigBlock empty;
for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
ret[i] = oss_stat_device(default_devices[i], &err[i]);
if (ret[i] == OSS_STAT_NO_ERROR) {
- OssOutput *od = new OssOutput();
+ OssOutput *od = new OssOutput(default_devices[i]);
if (!od->Initialize(empty, error)) {
delete od;
- return NULL;
+ return nullptr;
}
- od->device = default_devices[i];
- return &od->base;
+ return od;
}
}
@@ -194,62 +232,33 @@ oss_open_default(Error &error)
error.Set(oss_output_domain,
"error trying to open default OSS device");
- return NULL;
+ return nullptr;
}
-static AudioOutput *
-oss_output_init(const config_param &param, Error &error)
+inline OssOutput *
+OssOutput::Create(const ConfigBlock &block, Error &error)
{
- const char *device = param.GetBlockValue("device");
- if (device != NULL) {
+ const char *device = block.GetBlockValue("device");
+ if (device != nullptr) {
OssOutput *od = new OssOutput();
- if (!od->Initialize(param, error)) {
+ if (!od->Initialize(block, error)) {
delete od;
- return NULL;
+ return nullptr;
}
od->device = device;
- return &od->base;
+ return od;
}
return oss_open_default(error);
}
-static void
-oss_output_finish(AudioOutput *ao)
+void
+OssOutput::DoClose()
{
- OssOutput *od = (OssOutput *)ao;
-
- delete od;
-}
-
-#ifdef AFMT_S24_PACKED
-
-static bool
-oss_output_enable(AudioOutput *ao, gcc_unused Error &error)
-{
- OssOutput *od = (OssOutput *)ao;
-
- od->pcm_export.Construct();
- return true;
-}
-
-static void
-oss_output_disable(AudioOutput *ao)
-{
- OssOutput *od = (OssOutput *)ao;
-
- od->pcm_export.Destruct();
-}
-
-#endif
-
-static void
-oss_close(OssOutput *od)
-{
- if (od->fd >= 0)
- close(od->fd);
- od->fd = -1;
+ if (fd >= 0)
+ close(fd);
+ fd = -1;
}
/**
@@ -271,8 +280,8 @@ oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
const char *msg, Error &error)
{
assert(fd >= 0);
- assert(value_r != NULL);
- assert(msg != NULL);
+ assert(value_r != nullptr);
+ assert(msg != nullptr);
assert(!error.IsDefined());
int ret = ioctl(fd, request, value_r);
@@ -380,7 +389,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)
@@ -412,6 +421,7 @@ oss_setup_sample_rate(int fd, AudioFormat &audio_format,
* Convert a MPD sample format to its OSS counterpart. Returns
* AFMT_QUERY if there is no direct counterpart.
*/
+gcc_const
static int
sample_format_to_oss(SampleFormat format)
{
@@ -442,13 +452,15 @@ sample_format_to_oss(SampleFormat format)
#endif
}
- return AFMT_QUERY;
+ assert(false);
+ gcc_unreachable();
}
/**
* Convert an OSS sample format to its MPD counterpart. Returns
* SampleFormat::UNDEFINED if there is no direct counterpart.
*/
+gcc_const
static SampleFormat
sample_format_from_oss(int format)
{
@@ -572,7 +584,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,
@@ -609,18 +621,14 @@ oss_setup_sample_format(int fd, AudioFormat &audio_format,
return false;
}
-/**
- * Sets up the OSS device which was opened before.
- */
-static bool
-oss_setup(OssOutput *od, AudioFormat &audio_format,
- Error &error)
+inline bool
+OssOutput::Setup(AudioFormat &_audio_format, Error &error)
{
- return oss_setup_channels(od->fd, audio_format, error) &&
- oss_setup_sample_rate(od->fd, audio_format, error) &&
- oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+ return oss_setup_channels(fd, _audio_format, error) &&
+ oss_setup_sample_rate(fd, _audio_format, error) &&
+ oss_setup_sample_format(fd, _audio_format, &oss_format,
#ifdef AFMT_S24_PACKED
- od->pcm_export,
+ pcm_export,
#endif
error);
}
@@ -628,46 +636,46 @@ oss_setup(OssOutput *od, AudioFormat &audio_format,
/**
* Reopen the device with the saved audio_format, without any probing.
*/
-static bool
-oss_reopen(OssOutput *od, Error &error)
+inline bool
+OssOutput::Reopen(Error &error)
{
- assert(od->fd < 0);
+ assert(fd < 0);
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
+ fd = open_cloexec(device, O_WRONLY, 0);
+ if (fd < 0) {
error.FormatErrno("Error opening OSS device \"%s\"",
- od->device);
+ device);
return false;
}
enum oss_setup_result result;
const char *const msg1 = "Failed to set channel count";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
- od->audio_format.channels, msg1, error);
+ result = oss_try_ioctl(fd, SNDCTL_DSP_CHANNELS,
+ audio_format.channels, msg1, error);
if (result != SUCCESS) {
- oss_close(od);
+ DoClose();
if (result == UNSUPPORTED)
error.Set(oss_output_domain, msg1);
return false;
}
const char *const msg2 = "Failed to set sample rate";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
- od->audio_format.sample_rate, msg2, error);
+ result = oss_try_ioctl(fd, SNDCTL_DSP_SPEED,
+ audio_format.sample_rate, msg2, error);
if (result != SUCCESS) {
- oss_close(od);
+ DoClose();
if (result == UNSUPPORTED)
error.Set(oss_output_domain, msg2);
return false;
}
const char *const msg3 = "Failed to set sample format";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
- od->oss_format,
+ result = oss_try_ioctl(fd, SNDCTL_DSP_SAMPLESIZE,
+ oss_format,
msg3, error);
if (result != SUCCESS) {
- oss_close(od);
+ DoClose();
if (result == UNSUPPORTED)
error.Set(oss_output_domain, msg3);
return false;
@@ -676,62 +684,47 @@ oss_reopen(OssOutput *od, Error &error)
return true;
}
-static bool
-oss_output_open(AudioOutput *ao, AudioFormat &audio_format,
- Error &error)
+inline bool
+OssOutput::Open(AudioFormat &_audio_format, Error &error)
{
- OssOutput *od = (OssOutput *)ao;
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
+ fd = open_cloexec(device, O_WRONLY, 0);
+ if (fd < 0) {
error.FormatErrno("Error opening OSS device \"%s\"",
- od->device);
+ device);
return false;
}
- if (!oss_setup(od, audio_format, error)) {
- oss_close(od);
+ if (!Setup(_audio_format, error)) {
+ DoClose();
return false;
}
- od->audio_format = audio_format;
+ audio_format = _audio_format;
return true;
}
-static void
-oss_output_close(AudioOutput *ao)
+inline void
+OssOutput::Cancel()
{
- OssOutput *od = (OssOutput *)ao;
-
- oss_close(od);
-}
-
-static void
-oss_output_cancel(AudioOutput *ao)
-{
- OssOutput *od = (OssOutput *)ao;
-
- if (od->fd >= 0) {
- ioctl(od->fd, SNDCTL_DSP_RESET, 0);
- oss_close(od);
+ if (fd >= 0) {
+ ioctl(fd, SNDCTL_DSP_RESET, 0);
+ DoClose();
}
}
-static size_t
-oss_output_play(AudioOutput *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+OssOutput::Play(const void *chunk, size_t size, Error &error)
{
- OssOutput *od = (OssOutput *)ao;
ssize_t ret;
assert(size > 0);
/* reopen the device since it was closed by dropBufferedAudio */
- if (od->fd < 0 && !oss_reopen(od, error))
+ if (fd < 0 && !Reopen(error))
return 0;
#ifdef AFMT_S24_PACKED
- const auto e = od->pcm_export->Export({chunk, size});
+ const auto e = pcm_export->Export({chunk, size});
chunk = e.data;
size = e.size;
#endif
@@ -739,40 +732,42 @@ oss_output_play(AudioOutput *ao, const void *chunk, size_t size,
assert(size > 0);
while (true) {
- ret = write(od->fd, chunk, size);
+ ret = write(fd, chunk, size);
if (ret > 0) {
#ifdef AFMT_S24_PACKED
- ret = od->pcm_export->CalcSourceSize(ret);
+ ret = pcm_export->CalcSourceSize(ret);
#endif
return ret;
}
if (ret < 0 && errno != EINTR) {
- error.FormatErrno("Write error on %s", od->device);
+ error.FormatErrno("Write error on %s", device);
return 0;
}
}
}
+typedef AudioOutputWrapper<OssOutput> Wrapper;
+
const struct AudioOutputPlugin oss_output_plugin = {
"oss",
oss_output_test_default_device,
- oss_output_init,
- oss_output_finish,
+ &Wrapper::Init,
+ &Wrapper::Finish,
#ifdef AFMT_S24_PACKED
- oss_output_enable,
- oss_output_disable,
+ &Wrapper::Enable,
+ &Wrapper::Disable,
#else
nullptr,
nullptr,
#endif
- oss_output_open,
- oss_output_close,
+ &Wrapper::Open,
+ &Wrapper::Close,
nullptr,
nullptr,
- oss_output_play,
+ &Wrapper::Play,
nullptr,
- oss_output_cancel,
+ &Wrapper::Cancel,
nullptr,
&oss_mixer_plugin,
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 d8075d505..c70e2d498 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"
@@ -27,7 +28,9 @@
#include <stdio.h>
-struct PipeOutput {
+class PipeOutput {
+ friend struct AudioOutputWrapper<PipeOutput>;
+
AudioOutput base;
std::string cmd;
@@ -36,17 +39,27 @@ struct PipeOutput {
PipeOutput()
:base(pipe_output_plugin) {}
- bool Initialize(const config_param &param, Error &error) {
- return base.Configure(param, error);
+ bool Configure(const ConfigBlock &block, Error &error);
+
+public:
+ static PipeOutput *Create(const ConfigBlock &block, Error &error);
+
+ bool Open(AudioFormat &audio_format, Error &error);
+
+ void Close() {
+ pclose(fh);
}
- bool Configure(const config_param &param, Error &error);
+ size_t Play(const void *chunk, size_t size, Error &error);
};
inline bool
-PipeOutput::Configure(const config_param &param, Error &error)
+PipeOutput::Configure(const ConfigBlock &block, Error &error)
{
- cmd = param.GetBlockValue("command", "");
+ if (!base.Configure(block, error))
+ return false;
+
+ cmd = block.GetBlockValue("command", "");
if (cmd.empty()) {
error.Set(config_domain,
"No \"command\" parameter specified");
@@ -56,83 +69,56 @@ PipeOutput::Configure(const config_param &param, Error &error)
return true;
}
-static AudioOutput *
-pipe_output_init(const config_param &param, Error &error)
+inline PipeOutput *
+PipeOutput::Create(const ConfigBlock &block, Error &error)
{
- PipeOutput *pd = new PipeOutput();
+ PipeOutput *po = new PipeOutput();
- if (!pd->Initialize(param, error)) {
- delete pd;
+ if (!po->Configure(block, error)) {
+ delete po;
return nullptr;
}
- if (!pd->Configure(param, error)) {
- delete pd;
- 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)
-{
- PipeOutput *pd = (PipeOutput *)ao;
-
- pclose(pd->fh);
-}
-
-static size_t
-pipe_output_play(AudioOutput *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+PipeOutput::Play(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..8b5225584 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 ConfigBlock &block, Error &error);
+ static PulseOutput *Create(const ConfigBlock &block, 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 = &pm;
+ 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 &param, Error &error)
+inline bool
+PulseOutput::Configure(const ConfigBlock &block, Error &error)
{
- PulseOutput *po;
+ if (!base.Configure(block, error))
+ return false;
+
+ name = block.GetBlockValue("name", "mpd_pulse");
+ server = block.GetBlockValue("server");
+ sink = block.GetBlockValue("sink");
+ return true;
+}
+
+PulseOutput *
+PulseOutput::Create(const ConfigBlock &block, 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(block, 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());
+ const ConfigBlock empty;
+ 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..4f9231c70 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,21 +20,28 @@
#include "config.h"
#include "RecorderOutputPlugin.hxx"
#include "../OutputAPI.hxx"
+#include "../Wrapper.hxx"
+#include "tag/Format.hxx"
+#include "encoder/ToOutputStream.hxx"
+#include "encoder/EncoderInterface.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "config/ConfigError.hxx"
+#include "config/ConfigPath.hxx"
+#include "Log.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/io/FileOutputStream.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "system/fd_util.h"
-#include "open.h"
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
+#include <stdlib.h>
+
+static constexpr Domain recorder_domain("recorder");
+
+class RecorderOutput {
+ friend struct AudioOutputWrapper<RecorderOutput>;
-struct RecorderOutput {
AudioOutput base;
/**
@@ -45,44 +52,77 @@ struct RecorderOutput {
/**
* The destination file name.
*/
- const char *path;
+ AllocatedPath path;
+
+ /**
+ * A string that will be used with FormatTag() to build the
+ * destination path.
+ */
+ std::string format_path;
/**
- * The destination file descriptor.
+ * The #AudioFormat that is currently active. This is used
+ * for switching to another file.
*/
- int fd;
+ AudioFormat effective_audio_format;
/**
- * The buffer for encoder_read().
+ * The destination file.
*/
- char buffer[32768];
+ FileOutputStream *file;
RecorderOutput()
- :base(recorder_output_plugin) {}
+ :base(recorder_output_plugin),
+ encoder(nullptr),
+ path(AllocatedPath::Null()) {}
- bool Initialize(const config_param &param, Error &error_r) {
- return base.Configure(param, error_r);
+ ~RecorderOutput() {
+ if (encoder != nullptr)
+ encoder->Dispose();
}
- bool Configure(const config_param &param, Error &error);
+ bool Initialize(const ConfigBlock &block, Error &error_r) {
+ return base.Configure(block, error_r);
+ }
+
+ static RecorderOutput *Create(const ConfigBlock &block, Error &error);
+
+ bool Configure(const ConfigBlock &block, Error &error);
- bool WriteToFile(const void *data, size_t length, Error &error);
+ bool Open(AudioFormat &audio_format, Error &error);
+ void Close();
/**
* Writes pending data from the encoder to the output file.
*/
bool EncoderToFile(Error &error);
-};
-static constexpr Domain recorder_output_domain("recorder_output");
+ void SendTag(const Tag &tag);
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+private:
+ gcc_pure
+ bool HasDynamicPath() const {
+ return !format_path.empty();
+ }
+
+ /**
+ * Finish the encoder and commit the file.
+ */
+ bool Commit(Error &error);
+
+ void FinishFormat();
+ bool ReopenFormat(AllocatedPath &&new_path, Error &error);
+};
inline bool
-RecorderOutput::Configure(const config_param &param, Error &error)
+RecorderOutput::Configure(const ConfigBlock &block, Error &error)
{
/* read configuration */
const char *encoder_name =
- param.GetBlockValue("encoder", "vorbis");
+ block.GetBlockValue("encoder", "vorbis");
const auto encoder_plugin = encoder_plugin_get(encoder_name);
if (encoder_plugin == nullptr) {
error.Format(config_domain,
@@ -90,167 +130,269 @@ RecorderOutput::Configure(const config_param &param, Error &error)
return false;
}
- path = param.GetBlockValue("path");
- if (path == nullptr) {
+ path = block.GetBlockPath("path", error);
+ if (error.IsDefined())
+ return false;
+
+ const char *fmt = block.GetBlockValue("format_path", nullptr);
+ if (fmt != nullptr)
+ format_path = fmt;
+
+ if (path.IsNull() && fmt == nullptr) {
error.Set(config_domain, "'path' not configured");
return false;
}
+ if (!path.IsNull() && fmt != nullptr) {
+ error.Set(config_domain, "Cannot have both 'path' and 'format_path'");
+ return false;
+ }
+
/* initialize encoder */
- encoder = encoder_init(*encoder_plugin, param, error);
+ encoder = encoder_init(*encoder_plugin, block, error);
if (encoder == nullptr)
return false;
return true;
}
-static AudioOutput *
-recorder_output_init(const config_param &param, Error &error)
+RecorderOutput *
+RecorderOutput::Create(const ConfigBlock &block, Error &error)
{
RecorderOutput *recorder = new RecorderOutput();
- if (!recorder->Initialize(param, error)) {
+ if (!recorder->Initialize(block, error)) {
delete recorder;
return nullptr;
}
- if (!recorder->Configure(param, error)) {
+ if (!recorder->Configure(block, error)) {
delete recorder;
return nullptr;
}
- return &recorder->base;
+ return recorder;
}
-static void
-recorder_output_finish(AudioOutput *ao)
+inline bool
+RecorderOutput::EncoderToFile(Error &error)
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
+ assert(file != nullptr);
+ assert(file->IsDefined());
- encoder_finish(recorder->encoder);
- delete recorder;
+ return EncoderToOutputStream(*file, *encoder, error);
}
inline bool
-RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error)
+RecorderOutput::Open(AudioFormat &audio_format, Error &error)
{
- assert(length > 0);
-
- const uint8_t *data = (const uint8_t *)_data, *end = data + length;
-
- while (true) {
- ssize_t nbytes = write(fd, data, end - data);
- if (nbytes > 0) {
- data += nbytes;
- if (data == end)
- return true;
- } else if (nbytes == 0) {
- /* shouldn't happen for files */
- error.Set(recorder_output_domain,
- "write() returned 0");
+ /* create the output file */
+
+ if (!HasDynamicPath()) {
+ assert(!path.IsNull());
+
+ file = FileOutputStream::Create(path, error);
+ if (file == nullptr)
return false;
- } else if (errno != EINTR) {
- error.FormatErrno("Failed to write to '%s'", path);
+ } else {
+ /* don't open the file just yet; wait until we have
+ a tag that we can use to build the path */
+ assert(path.IsNull());
+
+ file = nullptr;
+ }
+
+ /* open the encoder */
+
+ if (!encoder->Open(audio_format, error)) {
+ delete file;
+ return false;
+ }
+
+ if (!HasDynamicPath()) {
+ if (!EncoderToFile(error)) {
+ encoder->Close();
+ delete file;
return false;
}
+ } else {
+ /* remember the AudioFormat for ReopenFormat() */
+ effective_audio_format = audio_format;
+
+ /* close the encoder for now; it will be opened as
+ soon as we have received a tag */
+ encoder->Close();
}
+
+ return true;
}
inline bool
-RecorderOutput::EncoderToFile(Error &error)
+RecorderOutput::Commit(Error &error)
{
- assert(fd >= 0);
+ assert(!path.IsNull());
- while (true) {
- /* read from the encoder */
+ /* flush the encoder and write the rest to the file */
- size_t size = encoder_read(encoder, buffer, sizeof(buffer));
- if (size == 0)
- return true;
+ bool success = encoder_end(encoder, error) &&
+ EncoderToFile(error);
- /* write everything into the file */
+ /* now really close everything */
- if (!WriteToFile(buffer, size, error))
- return false;
- }
+ encoder->Close();
+
+ if (success && !file->Commit(error))
+ success = false;
+
+ delete file;
+
+ return success;
}
-static bool
-recorder_output_open(AudioOutput *ao,
- AudioFormat &audio_format,
- Error &error)
+inline void
+RecorderOutput::Close()
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
+ if (file == nullptr) {
+ /* not currently encoding to a file; nothing needs to
+ be done now */
+ assert(HasDynamicPath());
+ assert(path.IsNull());
+ return;
+ }
- /* create the output file */
+ Error error;
+ if (!Commit(error))
+ LogError(error);
- recorder->fd = open_cloexec(recorder->path,
- O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
- 0666);
- if (recorder->fd < 0) {
- error.FormatErrno("Failed to create '%s'", recorder->path);
- return false;
+ if (HasDynamicPath()) {
+ assert(!path.IsNull());
+ path.SetNull();
}
+}
- /* open the encoder */
+void
+RecorderOutput::FinishFormat()
+{
+ assert(HasDynamicPath());
+
+ if (file == nullptr)
+ return;
+
+ Error error;
+ if (!Commit(error))
+ LogError(error);
+
+ file = nullptr;
+ path.SetNull();
+}
- if (!encoder_open(recorder->encoder, audio_format, error)) {
- close(recorder->fd);
- unlink(recorder->path);
+inline bool
+RecorderOutput::ReopenFormat(AllocatedPath &&new_path, Error &error)
+{
+ assert(HasDynamicPath());
+ assert(path.IsNull());
+ assert(file == nullptr);
+
+ FileOutputStream *new_file =
+ FileOutputStream::Create(new_path, error);
+ if (new_file == nullptr)
+ return false;
+
+ AudioFormat new_audio_format = effective_audio_format;
+ if (!encoder->Open(new_audio_format, error)) {
+ delete new_file;
return false;
}
- if (!recorder->EncoderToFile(error)) {
- encoder_close(recorder->encoder);
- close(recorder->fd);
- unlink(recorder->path);
+ /* reopening the encoder must always result in the same
+ AudioFormat as before */
+ assert(new_audio_format == effective_audio_format);
+
+ if (!EncoderToOutputStream(*new_file, *encoder, error)) {
+ encoder->Close();
+ delete new_file;
return false;
}
+ path = std::move(new_path);
+ file = new_file;
+
+ FormatDebug(recorder_domain, "Recording to \"%s\"",
+ path.ToUTF8().c_str());
+
return true;
}
-static void
-recorder_output_close(AudioOutput *ao)
+inline void
+RecorderOutput::SendTag(const Tag &tag)
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
-
- /* flush the encoder and write the rest to the file */
+ if (HasDynamicPath()) {
+ char *p = FormatTag(tag, format_path.c_str());
+ if (p == nullptr || *p == 0) {
+ /* no path could be composed with this tag:
+ don't write a file */
+ free(p);
+ FinishFormat();
+ return;
+ }
- if (encoder_end(recorder->encoder, IgnoreError()))
- recorder->EncoderToFile(IgnoreError());
+ Error error;
+ AllocatedPath new_path = ParsePath(p, error);
+ free(p);
+ if (new_path.IsNull()) {
+ LogError(error);
+ FinishFormat();
+ return;
+ }
- /* now really close everything */
+ if (new_path != path) {
+ FinishFormat();
- encoder_close(recorder->encoder);
+ if (!ReopenFormat(std::move(new_path), error)) {
+ LogError(error);
+ return;
+ }
+ }
+ }
- close(recorder->fd);
+ Error error;
+ if (!encoder_pre_tag(encoder, error) ||
+ !EncoderToFile(error) ||
+ !encoder_tag(encoder, tag, error))
+ LogError(error);
}
-static size_t
-recorder_output_play(AudioOutput *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+RecorderOutput::Play(const void *chunk, size_t size, Error &error)
{
- RecorderOutput *recorder = (RecorderOutput *)ao;
+ if (file == nullptr) {
+ /* not currently encoding to a file; discard incoming
+ data */
+ assert(HasDynamicPath());
+ assert(path.IsNull());
+ return size;
+ }
- return encoder_write(recorder->encoder, chunk, size, error) &&
- recorder->EncoderToFile(error)
+ return encoder_write(encoder, chunk, size, error) &&
+ EncoderToFile(error)
? size : 0;
}
+typedef AudioOutputWrapper<RecorderOutput> Wrapper;
+
const struct AudioOutputPlugin recorder_output_plugin = {
"recorder",
nullptr,
- recorder_output_init,
- recorder_output_finish,
- nullptr,
+ &Wrapper::Init,
+ &Wrapper::Finish,
nullptr,
- recorder_output_open,
- recorder_output_close,
nullptr,
+ &Wrapper::Open,
+ &Wrapper::Close,
nullptr,
- recorder_output_play,
+ &Wrapper::SendTag,
+ &Wrapper::Play,
nullptr,
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..11b0f1671 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;
@@ -57,11 +60,11 @@ public:
return &base;
}
- bool Initialize(const config_param &param, Error &error) {
- return base.Configure(param, error);
+ bool Initialize(const ConfigBlock &block, Error &error) {
+ return base.Configure(block, error);
}
- void Configure(const config_param &param);
+ void Configure(const ConfigBlock &block);
bool Open(AudioFormat &audio_format, Error &error);
void Close();
@@ -121,40 +124,32 @@ roar_output_set_volume(RoarOutput &roar, unsigned volume)
}
inline void
-RoarOutput::Configure(const config_param &param)
+RoarOutput::Configure(const ConfigBlock &block)
{
- host = param.GetBlockValue("server", "");
- name = param.GetBlockValue("name", "MPD");
+ host = block.GetBlockValue("server", "");
+ name = block.GetBlockValue("name", "MPD");
- const char *_role = param.GetBlockValue("role", "music");
+ const char *_role = block.GetBlockValue("role", "music");
role = _role != nullptr
? roar_str2role(_role)
: ROAR_ROLE_MUSIC;
}
static AudioOutput *
-roar_init(const config_param &param, Error &error)
+roar_init(const ConfigBlock &block, Error &error)
{
RoarOutput *self = new RoarOutput();
- if (!self->Initialize(param, error)) {
+ if (!self->Initialize(block, error)) {
delete self;
return nullptr;
}
- self->Configure(param);
+ self->Configure(block);
return *self;
}
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..339c4e491 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
@@ -20,6 +20,7 @@
#include "config.h"
#include "ShoutOutputPlugin.hxx"
#include "../OutputAPI.hxx"
+#include "encoder/EncoderInterface.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "config/ConfigError.hxx"
@@ -67,11 +68,11 @@ struct ShoutOutput final {
shout_free(shout_conn);
}
- bool Initialize(const config_param &param, Error &error) {
- return base.Configure(param, error);
+ bool Initialize(const ConfigBlock &block, Error &error) {
+ return base.Configure(block, error);
}
- bool Configure(const config_param &param, Error &error);
+ bool Configure(const ConfigBlock &block, Error &error);
};
static int shout_init_count;
@@ -91,18 +92,18 @@ shout_encoder_plugin_get(const char *name)
gcc_pure
static const char *
-require_block_string(const config_param &param, const char *name)
+require_block_string(const ConfigBlock &block, const char *name)
{
- const char *value = param.GetBlockValue(name);
+ const char *value = block.GetBlockValue(name);
if (value == nullptr)
FormatFatalError("no \"%s\" defined for shout device defined "
- "at line %u\n", name, param.line);
+ "at line %d\n", name, block.line);
return value;
}
inline bool
-ShoutOutput::Configure(const config_param &param, Error &error)
+ShoutOutput::Configure(const ConfigBlock &block, Error &error)
{
const AudioFormat audio_format = base.config_audio_format;
@@ -112,22 +113,22 @@ ShoutOutput::Configure(const config_param &param, Error &error)
return false;
}
- const char *host = require_block_string(param, "host");
- const char *mount = require_block_string(param, "mount");
- unsigned port = param.GetBlockValue("port", 0u);
+ const char *host = require_block_string(block, "host");
+ const char *mount = require_block_string(block, "mount");
+ unsigned port = block.GetBlockValue("port", 0u);
if (port == 0) {
error.Set(config_domain, "shout port must be configured");
return false;
}
- const char *passwd = require_block_string(param, "password");
- const char *name = require_block_string(param, "name");
+ const char *passwd = require_block_string(block, "password");
+ const char *name = require_block_string(block, "name");
- bool is_public = param.GetBlockValue("public", false);
+ bool is_public = block.GetBlockValue("public", false);
- const char *user = param.GetBlockValue("user", "source");
+ const char *user = block.GetBlockValue("user", "source");
- const char *value = param.GetBlockValue("quality");
+ const char *value = block.GetBlockValue("quality");
if (value != nullptr) {
char *test;
quality = strtod(value, &test);
@@ -140,14 +141,14 @@ ShoutOutput::Configure(const config_param &param, Error &error)
return false;
}
- if (param.GetBlockValue("bitrate") != nullptr) {
+ if (block.GetBlockValue("bitrate") != nullptr) {
error.Set(config_domain,
"quality and bitrate are "
"both defined");
return false;
}
} else {
- value = param.GetBlockValue("bitrate");
+ value = block.GetBlockValue("bitrate");
if (value == nullptr) {
error.Set(config_domain,
"neither bitrate nor quality defined");
@@ -164,7 +165,7 @@ ShoutOutput::Configure(const config_param &param, Error &error)
}
}
- const char *encoding = param.GetBlockValue("encoding", "ogg");
+ const char *encoding = block.GetBlockValue("encoding", "ogg");
const auto encoder_plugin = shout_encoder_plugin_get(encoding);
if (encoder_plugin == nullptr) {
error.Format(config_domain,
@@ -173,7 +174,7 @@ ShoutOutput::Configure(const config_param &param, Error &error)
return false;
}
- encoder = encoder_init(*encoder_plugin, param, error);
+ encoder = encoder_init(*encoder_plugin, block, error);
if (encoder == nullptr)
return false;
@@ -184,7 +185,7 @@ ShoutOutput::Configure(const config_param &param, Error &error)
shout_format = SHOUT_FORMAT_OGG;
unsigned protocol;
- value = param.GetBlockValue("protocol");
+ value = block.GetBlockValue("protocol");
if (value != nullptr) {
if (0 == strcmp(value, "shoutcast") &&
0 != strcmp(encoding, "mp3")) {
@@ -225,21 +226,21 @@ ShoutOutput::Configure(const config_param &param, Error &error)
}
/* optional paramters */
- timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT);
+ timeout = block.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT);
- value = param.GetBlockValue("genre");
+ value = block.GetBlockValue("genre");
if (value != nullptr && shout_set_genre(shout_conn, value)) {
error.Set(shout_output_domain, shout_get_error(shout_conn));
return false;
}
- value = param.GetBlockValue("description");
+ value = block.GetBlockValue("description");
if (value != nullptr && shout_set_description(shout_conn, value)) {
error.Set(shout_output_domain, shout_get_error(shout_conn));
return false;
}
- value = param.GetBlockValue("url");
+ value = block.GetBlockValue("url");
if (value != nullptr && shout_set_url(shout_conn, value)) {
error.Set(shout_output_domain, shout_get_error(shout_conn));
return false;
@@ -271,15 +272,15 @@ ShoutOutput::Configure(const config_param &param, Error &error)
}
static AudioOutput *
-my_shout_init_driver(const config_param &param, Error &error)
+my_shout_init_driver(const ConfigBlock &block, Error &error)
{
ShoutOutput *sd = new ShoutOutput();
- if (!sd->Initialize(param, error)) {
+ if (!sd->Initialize(block, error)) {
delete sd;
return nullptr;
}
- if (!sd->Configure(param, error)) {
+ if (!sd->Configure(block, error)) {
delete sd;
return nullptr;
}
@@ -345,7 +346,7 @@ static void close_shout_conn(ShoutOutput * sd)
if (encoder_end(sd->encoder, IgnoreError()))
write_page(sd, IgnoreError());
- encoder_close(sd->encoder);
+ sd->encoder->Close();
}
if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
@@ -361,7 +362,7 @@ my_shout_finish_driver(AudioOutput *ao)
{
ShoutOutput *sd = (ShoutOutput *)ao;
- encoder_finish(sd->encoder);
+ sd->encoder->Dispose();
delete sd;
@@ -415,13 +416,13 @@ my_shout_open_device(AudioOutput *ao, AudioFormat &audio_format,
if (!shout_connect(sd, error))
return false;
- if (!encoder_open(sd->encoder, audio_format, error)) {
+ if (!sd->encoder->Open(audio_format, error)) {
shout_close(sd->shout_conn);
return false;
}
if (!write_page(sd, error)) {
- encoder_close(sd->encoder);
+ sd->encoder->Close();
shout_close(sd->shout_conn);
return false;
}
@@ -462,7 +463,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 +471,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 +489,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..18c92d361 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
@@ -60,8 +60,8 @@ struct SolarisOutput {
SolarisOutput()
:base(solaris_output_plugin) {}
- bool Initialize(const config_param &param, Error &error_r) {
- return base.Configure(param, error_r);
+ bool Initialize(const ConfigBlock &block, Error &error_r) {
+ return base.Configure(block, error_r);
}
};
@@ -75,15 +75,15 @@ solaris_output_test_default_device(void)
}
static AudioOutput *
-solaris_output_init(const config_param &param, Error &error_r)
+solaris_output_init(const ConfigBlock &block, Error &error_r)
{
SolarisOutput *so = new SolarisOutput();
- if (!so->Initialize(param, error_r)) {
+ if (!so->Initialize(block, error_r)) {
delete so;
return nullptr;
}
- so->device = param.GetBlockValue("device", "/dev/audio");
+ so->device = block.GetBlockValue("device", "/dev/audio");
return &so->base;
}
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..35efb0f93 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
@@ -22,9 +22,11 @@
#include "../OutputAPI.hxx"
#include "pcm/PcmBuffer.hxx"
#include "mixer/MixerList.hxx"
+#include "fs/AllocatedPath.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
+#include "util/StringUtil.hxx"
#include <stdlib.h>
#include <string.h>
@@ -56,6 +58,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)
{
@@ -83,13 +97,23 @@ get_device_id(const char *device_name, UINT *device_id, Error &error)
char *endptr;
UINT id = strtoul(device_name, &endptr, 0);
if (endptr > device_name && *endptr == 0) {
- if (id >= numdevs)
- goto fail;
+ if (id >= numdevs) {
+ error.Format(winmm_output_domain,
+ "device \"%s\" is not found",
+ device_name);
+ return false;
+ }
+
*device_id = id;
return true;
}
/* check for device name */
+ const AllocatedPath device_name_fs =
+ AllocatedPath::FromUTF8(device_name, error);
+ if (device_name_fs.IsNull())
+ return false;
+
for (UINT i = 0; i < numdevs; i++) {
WAVEOUTCAPS caps;
MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
@@ -97,28 +121,27 @@ get_device_id(const char *device_name, UINT *device_id, Error &error)
continue;
/* szPname is only 32 chars long, so it is often truncated.
Use partial match to work around this. */
- if (strstr(device_name, caps.szPname) == device_name) {
+ if (StringStartsWith(device_name_fs.c_str(), caps.szPname)) {
*device_id = i;
return true;
}
}
-fail:
error.Format(winmm_output_domain,
"device \"%s\" is not found", device_name);
return false;
}
static AudioOutput *
-winmm_output_init(const config_param &param, Error &error)
+winmm_output_init(const ConfigBlock &block, Error &error)
{
WinmmOutput *wo = new WinmmOutput();
- if (!wo->base.Configure(param, error)) {
+ if (!wo->base.Configure(block, error)) {
delete wo;
return nullptr;
}
- const char *device = param.GetBlockValue("device");
+ const char *device = block.GetBlockValue("device");
if (!get_device_id(device, &wo->device_id, error)) {
delete wo;
return nullptr;
@@ -179,7 +202,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 +248,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 +274,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 +301,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..99210c1fd 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
@@ -23,7 +23,7 @@
#include "util/ASCII.hxx"
#include "Page.hxx"
#include "IcyMetaDataServer.hxx"
-#include "system/SocketError.hxx"
+#include "net/SocketError.hxx"
#include "Log.hxx"
#include <assert.h>
diff --git a/src/output/plugins/httpd/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx
index f94f05769..6646ddf4c 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
@@ -23,6 +23,8 @@
#include "event/BufferedSocket.hxx"
#include "Compiler.h"
+#include <boost/intrusive/list.hpp>
+
#include <queue>
#include <list>
@@ -31,7 +33,9 @@
class HttpdOutput;
class Page;
-class HttpdClient final : BufferedSocket {
+class HttpdClient final
+ : BufferedSocket,
+ public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
/**
* The httpd output object this client is connected to.
*/
@@ -124,7 +128,7 @@ class HttpdClient final : BufferedSocket {
public:
/**
* @param httpd the HTTP output device
- * @param fd the socket file descriptor
+ * @param _fd the socket file descriptor
*/
HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop,
bool _metadata_supported);
diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx
index 20ff15e42..01498dfcd 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
@@ -25,6 +25,7 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
+#include "HttpdClient.hxx"
#include "output/Internal.hxx"
#include "output/Timer.hxx"
#include "thread/Mutex.hxx"
@@ -33,16 +34,10 @@
#include "util/Cast.hxx"
#include "Compiler.h"
-#ifdef _LIBCPP_VERSION
-/* can't use incomplete template arguments with libc++ */
-#include "HttpdClient.hxx"
-#endif
-
-#include <forward_list>
#include <queue>
#include <list>
-struct config_param;
+struct ConfigBlock;
class Error;
class EventLoop;
class ServerSocket;
@@ -135,7 +130,8 @@ private:
* A linked list containing all clients which are currently
* connected.
*/
- std::forward_list<HttpdClient> clients;
+ boost::intrusive::list<HttpdClient,
+ boost::intrusive::constant_time_size<true>> clients;
/**
* A temporary buffer for the httpd_output_read_page()
@@ -147,13 +143,13 @@ private:
* The maximum and current number of clients connected
* at the same time.
*/
- unsigned clients_max, clients_cnt;
+ unsigned clients_max;
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) {
@@ -162,16 +158,16 @@ public:
using DeferredMonitor::GetEventLoop;
- bool Init(const config_param &param, Error &error);
+ bool Init(const ConfigBlock &block, Error &error);
- bool Configure(const config_param &param, Error &error);
+ bool Configure(const ConfigBlock &block, Error &error);
- AudioOutput *InitAndConfigure(const config_param &param,
+ AudioOutput *InitAndConfigure(const ConfigBlock &block,
Error &error) {
- if (!Init(param, error))
+ if (!Init(block, error))
return nullptr;
- if (!Configure(param, error))
+ if (!Configure(block, error))
return nullptr;
return &base;
@@ -250,7 +246,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);
@@ -259,8 +255,7 @@ public:
private:
virtual void RunDeferred() override;
- virtual void OnAccept(int fd, const sockaddr &address,
- size_t address_length, int uid) override;
+ void OnAccept(int fd, SocketAddress address, int uid) override;
};
extern const class Domain httpd_output_domain;
diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
index e3ba7727d..765a72d92 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
@@ -22,9 +22,11 @@
#include "HttpdInternal.hxx"
#include "HttpdClient.hxx"
#include "output/OutputAPI.hxx"
+#include "encoder/EncoderInterface.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
-#include "system/Resolver.hxx"
+#include "net/SocketAddress.hxx"
+#include "net/ToString.hxx"
#include "Page.hxx"
#include "IcyMetaDataServer.hxx"
#include "system/fd_util.h"
@@ -32,6 +34,7 @@
#include "event/Call.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "util/DeleteDisposer.hxx"
#include "Log.hxx"
#include <assert.h>
@@ -63,7 +66,7 @@ HttpdOutput::~HttpdOutput()
metadata->Unref();
if (encoder != nullptr)
- encoder_finish(encoder);
+ encoder->Dispose();
}
@@ -90,17 +93,17 @@ HttpdOutput::Unbind()
}
inline bool
-HttpdOutput::Configure(const config_param &param, Error &error)
+HttpdOutput::Configure(const ConfigBlock &block, Error &error)
{
/* read configuration */
- name = param.GetBlockValue("name", "Set name in config");
- genre = param.GetBlockValue("genre", "Set genre in config");
- website = param.GetBlockValue("website", "Set website in config");
+ name = block.GetBlockValue("name", "Set name in config");
+ genre = block.GetBlockValue("genre", "Set genre in config");
+ website = block.GetBlockValue("website", "Set website in config");
- unsigned port = param.GetBlockValue("port", 8000u);
+ unsigned port = block.GetBlockValue("port", 8000u);
const char *encoder_name =
- param.GetBlockValue("encoder", "vorbis");
+ block.GetBlockValue("encoder", "vorbis");
const auto encoder_plugin = encoder_plugin_get(encoder_name);
if (encoder_plugin == nullptr) {
error.Format(httpd_output_domain,
@@ -108,11 +111,11 @@ HttpdOutput::Configure(const config_param &param, Error &error)
return false;
}
- clients_max = param.GetBlockValue("max_clients", 0u);
+ clients_max = block.GetBlockValue("max_clients", 0u);
/* set up bind_to_address */
- const char *bind_to_address = param.GetBlockValue("bind_to_address");
+ const char *bind_to_address = block.GetBlockValue("bind_to_address");
bool success = bind_to_address != nullptr &&
strcmp(bind_to_address, "any") != 0
? AddHost(bind_to_address, port, error)
@@ -122,7 +125,7 @@ HttpdOutput::Configure(const config_param &param, Error &error)
/* initialize encoder */
- encoder = encoder_init(*encoder_plugin, param, error);
+ encoder = encoder_init(*encoder_plugin, block, error);
if (encoder == nullptr)
return false;
@@ -135,17 +138,17 @@ HttpdOutput::Configure(const config_param &param, Error &error)
}
inline bool
-HttpdOutput::Init(const config_param &param, Error &error)
+HttpdOutput::Init(const ConfigBlock &block, Error &error)
{
- return base.Configure(param, error);
+ return base.Configure(block, error);
}
static AudioOutput *
-httpd_output_init(const config_param &param, Error &error)
+httpd_output_init(const ConfigBlock &block, Error &error)
{
HttpdOutput *httpd = new HttpdOutput(io_thread_get());
- AudioOutput *result = httpd->InitAndConfigure(param, error);
+ AudioOutput *result = httpd->InitAndConfigure(block, error);
if (result == nullptr)
delete httpd;
@@ -167,9 +170,9 @@ httpd_output_finish(AudioOutput *ao)
inline void
HttpdOutput::AddClient(int fd)
{
- clients.emplace_front(*this, fd, GetEventLoop(),
- encoder->plugin.tag == nullptr);
- ++clients_cnt;
+ auto *client = new HttpdClient(*this, fd, GetEventLoop(),
+ encoder->plugin.tag == nullptr);
+ clients.push_front(*client);
/* pass metadata to client */
if (metadata != nullptr)
@@ -200,16 +203,14 @@ HttpdOutput::RunDeferred()
}
void
-HttpdOutput::OnAccept(int fd, const sockaddr &address,
- size_t address_length, gcc_unused int uid)
+HttpdOutput::OnAccept(int fd, SocketAddress address, gcc_unused int uid)
{
/* the listener socket has become readable - a client has
connected */
#ifdef HAVE_LIBWRAP
- if (address.sa_family != AF_UNIX) {
- const auto hostaddr = sockaddr_to_string(&address,
- address_length);
+ if (address.GetFamily() != AF_UNIX) {
+ const auto hostaddr = ToString(address);
// TODO: shall we obtain the program name from argv[0]?
const char *progname = "mpd";
@@ -229,14 +230,13 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address,
}
#else
(void)address;
- (void)address_length;
#endif /* HAVE_WRAP */
const ScopeLock protect(mutex);
if (fd >= 0) {
/* can we allow additional client */
- if (open && (clients_max == 0 || clients_cnt < clients_max))
+ if (open && (clients_max == 0 || clients.size() < clients_max))
AddClient(fd);
else
close_socket(fd);
@@ -294,7 +294,7 @@ httpd_output_disable(AudioOutput *ao)
inline bool
HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error)
{
- if (!encoder_open(encoder, audio_format, error))
+ if (!encoder->Open(audio_format, error))
return false;
/* we have to remember the encoder header, i.e. the first
@@ -320,7 +320,6 @@ HttpdOutput::Open(AudioFormat &audio_format, Error &error)
/* initialize other attributes */
- clients_cnt = 0;
timer = new Timer(audio_format);
open = true;
@@ -348,13 +347,13 @@ HttpdOutput::Close()
delete timer;
BlockingCall(GetEventLoop(), [this](){
- clients.clear();
+ clients.clear_and_dispose(DeleteDisposer());
});
if (header != nullptr)
header->Unref();
- encoder_close(encoder);
+ encoder->Close();
}
static void
@@ -369,17 +368,10 @@ httpd_output_close(AudioOutput *ao)
void
HttpdOutput::RemoveClient(HttpdClient &client)
{
- assert(clients_cnt > 0);
+ assert(!clients.empty());
- for (auto prev = clients.before_begin(), i = std::next(prev);;
- prev = i, i = std::next(prev)) {
- assert(i != clients.end());
- if (&*i == &client) {
- clients.erase_after(prev);
- clients_cnt--;
- break;
- }
- }
+ clients.erase_and_dispose(clients.iterator_to(client),
+ DeleteDisposer());
}
void
@@ -499,10 +491,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 +528,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 +538,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..1e23cd2cc 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;
@@ -88,11 +91,13 @@ public:
return &base;
}
- bool Initialize(const config_param &param, Error &error) {
- return base.Configure(param, error);
+ bool Initialize(const ConfigBlock &block, Error &error) {
+ return base.Configure(block, error);
}
- bool Configure(const config_param &param, Error &error);
+ bool Configure(const ConfigBlock &block, Error &error);
+
+ static SlesOutput *Create(const ConfigBlock &block, Error &error);
bool Open(AudioFormat &audio_format, Error &error);
void Close();
@@ -126,7 +131,7 @@ private:
static constexpr Domain sles_domain("sles");
inline bool
-SlesOutput::Configure(const config_param &, Error &)
+SlesOutput::Configure(const ConfigBlock &, Error &)
{
return true;
}
@@ -441,99 +446,36 @@ sles_test_default_device()
return true;
}
-static AudioOutput *
-sles_output_init(const config_param &param, Error &error)
+inline SlesOutput *
+SlesOutput::Create(const ConfigBlock &block, Error &error)
{
SlesOutput *sles = new SlesOutput();
- if (!sles->Initialize(param, error) ||
- !sles->Configure(param, error)) {
+ if (!sles->Initialize(block, error) ||
+ !sles->Configure(block, error)) {
delete sles;
return nullptr;
}
- 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);
+ return sles;
}
-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::Init,
+ &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