aboutsummaryrefslogtreecommitdiffstats
path: root/src/player
diff options
context:
space:
mode:
Diffstat (limited to 'src/player')
-rw-r--r--src/player/Control.cxx267
-rw-r--r--src/player/Control.hxx463
-rw-r--r--src/player/CrossFade.cxx140
-rw-r--r--src/player/CrossFade.hxx72
-rw-r--r--src/player/Listener.hxx36
-rw-r--r--src/player/Thread.cxx1208
-rw-r--r--src/player/Thread.hxx45
7 files changed, 2231 insertions, 0 deletions
diff --git a/src/player/Control.cxx b/src/player/Control.cxx
new file mode 100644
index 000000000..d7352ad57
--- /dev/null
+++ b/src/player/Control.cxx
@@ -0,0 +1,267 @@
+/*
+ * 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
+ * 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 "Control.hxx"
+#include "Idle.hxx"
+#include "DetachedSong.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+
+PlayerControl::PlayerControl(PlayerListener &_listener,
+ MultipleOutputs &_outputs,
+ unsigned _buffer_chunks,
+ unsigned _buffered_before_play)
+ :listener(_listener), outputs(_outputs),
+ buffer_chunks(_buffer_chunks),
+ buffered_before_play(_buffered_before_play),
+ command(PlayerCommand::NONE),
+ state(PlayerState::STOP),
+ error_type(PlayerError::NONE),
+ tagged_song(nullptr),
+ next_song(nullptr),
+ total_play_time(0),
+ border_pause(false)
+{
+}
+
+PlayerControl::~PlayerControl()
+{
+ delete next_song;
+ delete tagged_song;
+}
+
+void
+PlayerControl::Play(DetachedSong *song)
+{
+ assert(song != nullptr);
+
+ Lock();
+
+ if (state != PlayerState::STOP)
+ SynchronousCommand(PlayerCommand::STOP);
+
+ assert(next_song == nullptr);
+
+ EnqueueSongLocked(song);
+
+ assert(next_song == nullptr);
+
+ Unlock();
+}
+
+void
+PlayerControl::Cancel()
+{
+ LockSynchronousCommand(PlayerCommand::CANCEL);
+ assert(next_song == nullptr);
+}
+
+void
+PlayerControl::Stop()
+{
+ LockSynchronousCommand(PlayerCommand::CLOSE_AUDIO);
+ assert(next_song == nullptr);
+
+ idle_add(IDLE_PLAYER);
+}
+
+void
+PlayerControl::UpdateAudio()
+{
+ LockSynchronousCommand(PlayerCommand::UPDATE_AUDIO);
+}
+
+void
+PlayerControl::Kill()
+{
+ assert(thread.IsDefined());
+
+ LockSynchronousCommand(PlayerCommand::EXIT);
+ thread.Join();
+
+ idle_add(IDLE_PLAYER);
+}
+
+void
+PlayerControl::PauseLocked()
+{
+ if (state != PlayerState::STOP) {
+ SynchronousCommand(PlayerCommand::PAUSE);
+ idle_add(IDLE_PLAYER);
+ }
+}
+
+void
+PlayerControl::Pause()
+{
+ Lock();
+ PauseLocked();
+ Unlock();
+}
+
+void
+PlayerControl::SetPause(bool pause_flag)
+{
+ Lock();
+
+ switch (state) {
+ case PlayerState::STOP:
+ break;
+
+ case PlayerState::PLAY:
+ if (pause_flag)
+ PauseLocked();
+ break;
+
+ case PlayerState::PAUSE:
+ if (!pause_flag)
+ PauseLocked();
+ break;
+ }
+
+ Unlock();
+}
+
+void
+PlayerControl::SetBorderPause(bool _border_pause)
+{
+ Lock();
+ border_pause = _border_pause;
+ Unlock();
+}
+
+player_status
+PlayerControl::GetStatus()
+{
+ player_status status;
+
+ Lock();
+ SynchronousCommand(PlayerCommand::REFRESH);
+
+ status.state = state;
+
+ if (state != PlayerState::STOP) {
+ status.bit_rate = bit_rate;
+ status.audio_format = audio_format;
+ status.total_time = total_time;
+ status.elapsed_time = elapsed_time;
+ }
+
+ Unlock();
+
+ return status;
+}
+
+void
+PlayerControl::SetError(PlayerError type, Error &&_error)
+{
+ assert(type != PlayerError::NONE);
+ assert(_error.IsDefined());
+
+ error_type = type;
+ error = std::move(_error);
+}
+
+void
+PlayerControl::ClearError()
+{
+ Lock();
+
+ if (error_type != PlayerError::NONE) {
+ error_type = PlayerError::NONE;
+ error.Clear();
+ }
+
+ Unlock();
+}
+
+void
+PlayerControl::LockSetTaggedSong(const DetachedSong &song)
+{
+ Lock();
+ delete tagged_song;
+ tagged_song = new DetachedSong(song);
+ Unlock();
+}
+
+void
+PlayerControl::ClearTaggedSong()
+{
+ delete tagged_song;
+ tagged_song = nullptr;
+}
+
+void
+PlayerControl::EnqueueSong(DetachedSong *song)
+{
+ assert(song != nullptr);
+
+ Lock();
+ EnqueueSongLocked(song);
+ Unlock();
+}
+
+bool
+PlayerControl::Seek(DetachedSong *song, SongTime t)
+{
+ assert(song != nullptr);
+
+ Lock();
+
+ delete next_song;
+ next_song = song;
+ seek_time = t;
+ SynchronousCommand(PlayerCommand::SEEK);
+ Unlock();
+
+ assert(next_song == nullptr);
+
+ idle_add(IDLE_PLAYER);
+
+ return true;
+}
+
+void
+PlayerControl::SetCrossFade(float _cross_fade_seconds)
+{
+ if (_cross_fade_seconds < 0)
+ _cross_fade_seconds = 0;
+ cross_fade.duration = _cross_fade_seconds;
+
+ idle_add(IDLE_OPTIONS);
+}
+
+void
+PlayerControl::SetMixRampDb(float _mixramp_db)
+{
+ cross_fade.mixramp_db = _mixramp_db;
+
+ idle_add(IDLE_OPTIONS);
+}
+
+void
+PlayerControl::SetMixRampDelay(float _mixramp_delay_seconds)
+{
+ cross_fade.mixramp_delay = _mixramp_delay_seconds;
+
+ idle_add(IDLE_OPTIONS);
+}
diff --git a/src/player/Control.hxx b/src/player/Control.hxx
new file mode 100644
index 000000000..a2807a9a1
--- /dev/null
+++ b/src/player/Control.hxx
@@ -0,0 +1,463 @@
+/*
+ * 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
+ * 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_PLAYER_CONTROL_HXX
+#define MPD_PLAYER_CONTROL_HXX
+
+#include "AudioFormat.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "util/Error.hxx"
+#include "CrossFade.hxx"
+#include "Chrono.hxx"
+
+#include <stdint.h>
+
+class PlayerListener;
+class MultipleOutputs;
+class DetachedSong;
+
+enum class PlayerState : uint8_t {
+ STOP,
+ PAUSE,
+ PLAY
+};
+
+enum class PlayerCommand : uint8_t {
+ NONE,
+ EXIT,
+ STOP,
+ PAUSE,
+ SEEK,
+ CLOSE_AUDIO,
+
+ /**
+ * At least one AudioOutput.enabled flag has been modified;
+ * commit those changes to the output threads.
+ */
+ UPDATE_AUDIO,
+
+ /** PlayerControl.next_song has been updated */
+ QUEUE,
+
+ /**
+ * cancel pre-decoding PlayerControl.next_song; if the player
+ * has already started playing this song, it will completely
+ * stop
+ */
+ CANCEL,
+
+ /**
+ * Refresh status information in the #PlayerControl struct,
+ * e.g. elapsed_time.
+ */
+ REFRESH,
+};
+
+enum class PlayerError : uint8_t {
+ NONE,
+
+ /**
+ * The decoder has failed to decode the song.
+ */
+ DECODER,
+
+ /**
+ * The audio output has failed.
+ */
+ OUTPUT,
+};
+
+struct player_status {
+ PlayerState state;
+ uint16_t bit_rate;
+ AudioFormat audio_format;
+ SignedSongTime total_time;
+ SongTime elapsed_time;
+};
+
+struct PlayerControl {
+ PlayerListener &listener;
+
+ MultipleOutputs &outputs;
+
+ const unsigned buffer_chunks;
+
+ const unsigned buffered_before_play;
+
+ /**
+ * The handle of the player thread.
+ */
+ Thread thread;
+
+ /**
+ * This lock protects #command, #state, #error, #tagged_song.
+ */
+ mutable Mutex mutex;
+
+ /**
+ * Trigger this object after you have modified #command.
+ */
+ Cond cond;
+
+ /**
+ * This object gets signalled when the player thread has
+ * finished the #command. It wakes up the client that waits
+ * (i.e. the main thread).
+ */
+ Cond client_cond;
+
+ PlayerCommand command;
+ PlayerState state;
+
+ PlayerError error_type;
+
+ /**
+ * The error that occurred in the player thread. This
+ * attribute is only valid if #error is not
+ * #PlayerError::NONE. The object must be freed when this
+ * object transitions back to #PlayerError::NONE.
+ */
+ Error error;
+
+ /**
+ * A copy of the current #DetachedSong after its tags have
+ * been updated by the decoder (for example, a radio stream
+ * that has sent a new tag after switching to the next song).
+ * This shall be used by PlayerListener::OnPlayerTagModified()
+ * to update the current #DetachedSong in the queue.
+ *
+ * Protected by #mutex. Set by the PlayerThread and consumed
+ * by the main thread.
+ */
+ DetachedSong *tagged_song;
+
+ uint16_t bit_rate;
+ AudioFormat audio_format;
+ SignedSongTime total_time;
+ SongTime elapsed_time;
+
+ /**
+ * The next queued song.
+ *
+ * This is a duplicate, and must be freed when this attribute
+ * is cleared.
+ */
+ DetachedSong *next_song;
+
+ SongTime seek_time;
+
+ CrossFadeSettings cross_fade;
+
+ double total_play_time;
+
+ /**
+ * If this flag is set, then the player will be auto-paused at
+ * the end of the song, before the next song starts to play.
+ *
+ * This is a copy of the queue's "single" flag most of the
+ * time.
+ */
+ bool border_pause;
+
+ PlayerControl(PlayerListener &_listener,
+ MultipleOutputs &_outputs,
+ unsigned buffer_chunks,
+ unsigned buffered_before_play);
+ ~PlayerControl();
+
+ /**
+ * Locks the object.
+ */
+ void Lock() const {
+ mutex.lock();
+ }
+
+ /**
+ * Unlocks the object.
+ */
+ void Unlock() const {
+ mutex.unlock();
+ }
+
+ /**
+ * Signals the object. The object should be locked prior to
+ * calling this function.
+ */
+ void Signal() {
+ cond.signal();
+ }
+
+ /**
+ * Signals the object. The object is temporarily locked by
+ * this function.
+ */
+ void LockSignal() {
+ Lock();
+ Signal();
+ Unlock();
+ }
+
+ /**
+ * Waits for a signal on the object. This function is only
+ * valid in the player thread. The object must be locked
+ * prior to calling this function.
+ */
+ void Wait() {
+ assert(thread.IsInside());
+
+ cond.wait(mutex);
+ }
+
+ /**
+ * Wake up the client waiting for command completion.
+ *
+ * Caller must lock the object.
+ */
+ void ClientSignal() {
+ assert(thread.IsInside());
+
+ client_cond.signal();
+ }
+
+ /**
+ * The client calls this method to wait for command
+ * completion.
+ *
+ * Caller must lock the object.
+ */
+ void ClientWait() {
+ assert(!thread.IsInside());
+
+ client_cond.wait(mutex);
+ }
+
+ /**
+ * A command has been finished. This method clears the
+ * command and signals the client.
+ *
+ * To be called from the player thread. Caller must lock the
+ * object.
+ */
+ void CommandFinished() {
+ assert(command != PlayerCommand::NONE);
+
+ command = PlayerCommand::NONE;
+ ClientSignal();
+ }
+
+private:
+ /**
+ * Wait for the command to be finished by the player thread.
+ *
+ * To be called from the main thread. Caller must lock the
+ * object.
+ */
+ void WaitCommandLocked() {
+ while (command != PlayerCommand::NONE)
+ ClientWait();
+ }
+
+ /**
+ * Send a command to the player thread and synchronously wait
+ * for it to finish.
+ *
+ * To be called from the main thread. Caller must lock the
+ * object.
+ */
+ void SynchronousCommand(PlayerCommand cmd) {
+ assert(command == PlayerCommand::NONE);
+
+ command = cmd;
+ Signal();
+ WaitCommandLocked();
+ }
+
+ /**
+ * Send a command to the player thread and synchronously wait
+ * for it to finish.
+ *
+ * To be called from the main thread. This method locks the
+ * object.
+ */
+ void LockSynchronousCommand(PlayerCommand cmd) {
+ Lock();
+ SynchronousCommand(cmd);
+ Unlock();
+ }
+
+public:
+ /**
+ * @param song the song to be queued; the given instance will
+ * be owned and freed by the player
+ */
+ void Play(DetachedSong *song);
+
+ /**
+ * see PlayerCommand::CANCEL
+ */
+ void Cancel();
+
+ void SetPause(bool pause_flag);
+
+private:
+ void PauseLocked();
+
+public:
+ void Pause();
+
+ /**
+ * Set the player's #border_pause flag.
+ */
+ void SetBorderPause(bool border_pause);
+
+ void Kill();
+
+ gcc_pure
+ player_status GetStatus();
+
+ PlayerState GetState() const {
+ return state;
+ }
+
+ /**
+ * Set the error. Discards any previous error condition.
+ *
+ * Caller must lock the object.
+ *
+ * @param type the error type; must not be #PlayerError::NONE
+ * @param error detailed error information; must be defined.
+ */
+ void SetError(PlayerError type, Error &&error);
+
+ /**
+ * Checks whether an error has occurred, and if so, returns a
+ * copy of the #Error object.
+ *
+ * Caller must lock the object.
+ */
+ gcc_pure
+ Error GetError() const {
+ Error result;
+ if (error_type != PlayerError::NONE)
+ result.Set(error);
+ return result;
+ }
+
+ /**
+ * Like GetError(), but locks and unlocks the object.
+ */
+ gcc_pure
+ Error LockGetError() const {
+ Lock();
+ Error result = GetError();
+ Unlock();
+ return result;
+ }
+
+ void ClearError();
+
+ PlayerError GetErrorType() const {
+ return error_type;
+ }
+
+ /**
+ * Set the #tagged_song attribute to a newly allocated copy of
+ * the given #DetachedSong. Locks and unlocks the object.
+ */
+ void LockSetTaggedSong(const DetachedSong &song);
+
+ void ClearTaggedSong();
+
+ /**
+ * Read and clear the #tagged_song attribute.
+ *
+ * Caller must lock the object.
+ */
+ DetachedSong *ReadTaggedSong() {
+ DetachedSong *result = tagged_song;
+ tagged_song = nullptr;
+ return result;
+ }
+
+ /**
+ * Like ReadTaggedSong(), but locks and unlocks the object.
+ */
+ DetachedSong *LockReadTaggedSong() {
+ Lock();
+ DetachedSong *result = ReadTaggedSong();
+ Unlock();
+ return result;
+ }
+
+ void Stop();
+
+ void UpdateAudio();
+
+private:
+ void EnqueueSongLocked(DetachedSong *song) {
+ assert(song != nullptr);
+ assert(next_song == nullptr);
+
+ next_song = song;
+ SynchronousCommand(PlayerCommand::QUEUE);
+ }
+
+public:
+ /**
+ * @param song the song to be queued; the given instance will be owned
+ * and freed by the player
+ */
+ void EnqueueSong(DetachedSong *song);
+
+ /**
+ * Makes the player thread seek the specified song to a position.
+ *
+ * @param song the song to be queued; the given instance will be owned
+ * and freed by the player
+ * @return true on success, false on failure (e.g. if MPD isn't
+ * playing currently)
+ */
+ bool Seek(DetachedSong *song, SongTime t);
+
+ void SetCrossFade(float cross_fade_seconds);
+
+ float GetCrossFade() const {
+ return cross_fade.duration;
+ }
+
+ void SetMixRampDb(float mixramp_db);
+
+ float GetMixRampDb() const {
+ return cross_fade.mixramp_db;
+ }
+
+ void SetMixRampDelay(float mixramp_delay_seconds);
+
+ float GetMixRampDelay() const {
+ return cross_fade.mixramp_delay;
+ }
+
+ double GetTotalPlayTime() const {
+ return total_play_time;
+ }
+};
+
+#endif
diff --git a/src/player/CrossFade.cxx b/src/player/CrossFade.cxx
new file mode 100644
index 000000000..6d7b41440
--- /dev/null
+++ b/src/player/CrossFade.cxx
@@ -0,0 +1,140 @@
+/*
+ * 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
+ * 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 "CrossFade.hxx"
+#include "Chrono.hxx"
+#include "MusicChunk.hxx"
+#include "AudioFormat.hxx"
+#include "util/NumberParser.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+static constexpr Domain cross_fade_domain("cross_fade");
+
+gcc_pure
+static float
+mixramp_interpolate(const char *ramp_list, float required_db)
+{
+ float last_db = 0, last_secs = 0;
+ bool have_last = false;
+
+ /* ramp_list is a string of pairs of dBs and seconds that describe the
+ * volume profile. Delimiters are semi-colons between pairs and spaces
+ * between the dB and seconds of a pair.
+ * The dB values must be monotonically increasing for this to work. */
+
+ while (1) {
+ /* Parse the dB value. */
+ char *endptr;
+ const float db = ParseFloat(ramp_list, &endptr);
+ if (endptr == ramp_list || *endptr != ' ')
+ break;
+
+ ramp_list = endptr + 1;
+
+ /* Parse the time. */
+ float secs = ParseFloat(ramp_list, &endptr);
+ if (endptr == ramp_list || (*endptr != ';' && *endptr != 0))
+ break;
+
+ ramp_list = endptr;
+ if (*ramp_list == ';')
+ ++ramp_list;
+
+ /* Check for exact match. */
+ if (db == required_db) {
+ return secs;
+ }
+
+ /* Save if too quiet. */
+ if (db < required_db) {
+ last_db = db;
+ last_secs = secs;
+ have_last = true;
+ continue;
+ }
+
+ /* If required db < any stored value, use the least. */
+ if (!have_last)
+ return secs;
+
+ /* Finally, interpolate linearly. */
+ secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
+ return secs;
+ }
+
+ return -1;
+}
+
+unsigned
+CrossFadeSettings::Calculate(SignedSongTime total_time,
+ float replay_gain_db, float replay_gain_prev_db,
+ const char *mixramp_start, const char *mixramp_prev_end,
+ const AudioFormat af,
+ const AudioFormat old_format,
+ unsigned max_chunks) const
+{
+ unsigned int chunks = 0;
+ float chunks_f;
+
+ if (total_time.IsNegative() ||
+ duration < 0 || duration >= total_time.ToDoubleS() ||
+ /* we can't crossfade when the audio formats are different */
+ af != old_format)
+ return 0;
+
+ assert(duration >= 0);
+ assert(af.IsValid());
+
+ chunks_f = (float)af.GetTimeToSize() / (float)CHUNK_SIZE;
+
+ if (mixramp_delay <= 0 || !mixramp_start || !mixramp_prev_end) {
+ chunks = (chunks_f * duration + 0.5);
+ } else {
+ /* Calculate mixramp overlap. */
+ const float mixramp_overlap_current =
+ mixramp_interpolate(mixramp_start,
+ mixramp_db - replay_gain_db);
+ const float mixramp_overlap_prev =
+ mixramp_interpolate(mixramp_prev_end,
+ mixramp_db - replay_gain_prev_db);
+ const float mixramp_overlap =
+ mixramp_overlap_current + mixramp_overlap_prev;
+
+ if (mixramp_overlap_current >= 0 &&
+ mixramp_overlap_prev >= 0 &&
+ mixramp_delay <= mixramp_overlap) {
+ chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
+ FormatDebug(cross_fade_domain,
+ "will overlap %d chunks, %fs", chunks,
+ mixramp_overlap - mixramp_delay);
+ }
+ }
+
+ if (chunks > max_chunks) {
+ chunks = max_chunks;
+ LogWarning(cross_fade_domain,
+ "audio_buffer_size too small for computed MixRamp overlap");
+ }
+
+ return chunks;
+}
diff --git a/src/player/CrossFade.hxx b/src/player/CrossFade.hxx
new file mode 100644
index 000000000..672abb718
--- /dev/null
+++ b/src/player/CrossFade.hxx
@@ -0,0 +1,72 @@
+/*
+ * 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
+ * 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_CROSSFADE_HXX
+#define MPD_CROSSFADE_HXX
+
+#include "Compiler.h"
+
+struct AudioFormat;
+class SignedSongTime;
+
+struct CrossFadeSettings {
+ /**
+ * The configured cross fade duration [s].
+ */
+ float duration;
+
+ float mixramp_db;
+
+ /**
+ * The configured MixRapm delay [s]. A non-positive value
+ * disables MixRamp.
+ */
+ float mixramp_delay;
+
+ CrossFadeSettings()
+ :duration(0),
+ mixramp_db(0),
+ mixramp_delay(-1)
+ {}
+
+
+ /**
+ * Calculate how many music pipe chunks should be used for crossfading.
+ *
+ * @param total_time total_time the duration of the new song
+ * @param replay_gain_db the ReplayGain adjustment used for this song
+ * @param replay_gain_prev_db the ReplayGain adjustment used on the last song
+ * @param mixramp_start the next songs mixramp_start tag
+ * @param mixramp_prev_end the last songs mixramp_end setting
+ * @param af the audio format of the new song
+ * @param old_format the audio format of the current song
+ * @param max_chunks the maximum number of chunks
+ * @return the number of chunks for crossfading, or 0 if cross fading
+ * should be disabled for this song change
+ */
+ gcc_pure
+ unsigned Calculate(SignedSongTime total_time,
+ float replay_gain_db, float replay_gain_prev_db,
+ const char *mixramp_start,
+ const char *mixramp_prev_end,
+ AudioFormat af, AudioFormat old_format,
+ unsigned max_chunks) const;
+};
+
+#endif
diff --git a/src/player/Listener.hxx b/src/player/Listener.hxx
new file mode 100644
index 000000000..e10f2547b
--- /dev/null
+++ b/src/player/Listener.hxx
@@ -0,0 +1,36 @@
+/*
+ * 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
+ * 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_PLAYER_LISTENER_HXX
+#define MPD_PLAYER_LISTENER_HXX
+
+class PlayerListener {
+public:
+ /**
+ * Must call playlist_sync().
+ */
+ virtual void OnPlayerSync() = 0;
+
+ /**
+ * The current song's tag has changed.
+ */
+ virtual void OnPlayerTagModified() = 0;
+};
+
+#endif
diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx
new file mode 100644
index 000000000..60e253f4c
--- /dev/null
+++ b/src/player/Thread.cxx
@@ -0,0 +1,1208 @@
+/*
+ * 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
+ * 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 "Thread.hxx"
+#include "Listener.hxx"
+#include "decoder/DecoderThread.hxx"
+#include "decoder/DecoderControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "DetachedSong.hxx"
+#include "system/FatalError.hxx"
+#include "CrossFade.hxx"
+#include "Control.hxx"
+#include "output/MultipleOutputs.hxx"
+#include "tag/Tag.hxx"
+#include "Idle.hxx"
+#include "util/Domain.hxx"
+#include "thread/Name.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+static constexpr Domain player_domain("player");
+
+enum class CrossFadeState : int8_t {
+ DISABLED = -1,
+ UNKNOWN = 0,
+ ENABLED = 1
+};
+
+class Player {
+ PlayerControl &pc;
+
+ DecoderControl &dc;
+
+ MusicBuffer &buffer;
+
+ MusicPipe *pipe;
+
+ /**
+ * are we waiting for buffered_before_play?
+ */
+ bool buffering;
+
+ /**
+ * true if the decoder is starting and did not provide data
+ * yet
+ */
+ bool decoder_starting;
+
+ /**
+ * Did we wake up the DecoderThread recently? This avoids
+ * duplicate wakeup calls.
+ */
+ bool decoder_woken;
+
+ /**
+ * is the player paused?
+ */
+ bool paused;
+
+ /**
+ * is there a new song in pc.next_song?
+ */
+ bool queued;
+
+ /**
+ * Was any audio output opened successfully? It might have
+ * failed meanwhile, but was not explicitly closed by the
+ * player thread. When this flag is unset, some output
+ * methods must not be called.
+ */
+ bool output_open;
+
+ /**
+ * the song currently being played
+ */
+ DetachedSong *song;
+
+ /**
+ * is cross fading enabled?
+ */
+ CrossFadeState xfade_state;
+
+ /**
+ * has cross-fading begun?
+ */
+ bool cross_fading;
+
+ /**
+ * The number of chunks used for crossfading.
+ */
+ unsigned cross_fade_chunks;
+
+ /**
+ * The tag of the "next" song during cross-fade. It is
+ * postponed, and sent to the output thread when the new song
+ * really begins.
+ */
+ Tag *cross_fade_tag;
+
+ /**
+ * The current audio format for the audio outputs.
+ */
+ AudioFormat play_audio_format;
+
+ /**
+ * The time stamp of the chunk most recently sent to the
+ * output thread. This attribute is only used if
+ * MultipleOutputs::GetElapsedTime() didn't return a usable
+ * value; the output thread can estimate the elapsed time more
+ * precisely.
+ */
+ SongTime elapsed_time;
+
+public:
+ Player(PlayerControl &_pc, DecoderControl &_dc,
+ MusicBuffer &_buffer)
+ :pc(_pc), dc(_dc), buffer(_buffer),
+ buffering(true),
+ decoder_starting(false),
+ decoder_woken(false),
+ paused(false),
+ queued(true),
+ output_open(false),
+ song(nullptr),
+ xfade_state(CrossFadeState::UNKNOWN),
+ cross_fading(false),
+ cross_fade_chunks(0),
+ cross_fade_tag(nullptr),
+ elapsed_time(SongTime::zero()) {}
+
+private:
+ void ClearAndDeletePipe() {
+ pipe->Clear(buffer);
+ delete pipe;
+ }
+
+ void ClearAndReplacePipe(MusicPipe *_pipe) {
+ ClearAndDeletePipe();
+ pipe = _pipe;
+ }
+
+ void ReplacePipe(MusicPipe *_pipe) {
+ delete pipe;
+ pipe = _pipe;
+ }
+
+ /**
+ * Start the decoder.
+ *
+ * Player lock is not held.
+ */
+ void StartDecoder(MusicPipe &pipe);
+
+ /**
+ * The decoder has acknowledged the "START" command (see
+ * player::WaitForDecoder()). This function checks if the decoder
+ * initialization has completed yet.
+ *
+ * The player lock is not held.
+ */
+ bool CheckDecoderStartup();
+
+ /**
+ * Stop the decoder and clears (and frees) its music pipe.
+ *
+ * Player lock is not held.
+ */
+ void StopDecoder();
+
+ /**
+ * Is the decoder still busy on the same song as the player?
+ *
+ * Note: this function does not check if the decoder is already
+ * finished.
+ */
+ gcc_pure
+ bool IsDecoderAtCurrentSong() const {
+ assert(pipe != nullptr);
+
+ return dc.pipe == pipe;
+ }
+
+ /**
+ * Returns true if the decoder is decoding the next song (or has begun
+ * decoding it, or has finished doing it), and the player hasn't
+ * switched to that song yet.
+ */
+ gcc_pure
+ bool IsDecoderAtNextSong() const {
+ return dc.pipe != nullptr && !IsDecoderAtCurrentSong();
+ }
+
+ /**
+ * This is the handler for the #PlayerCommand::SEEK command.
+ *
+ * The player lock is not held.
+ */
+ bool SeekDecoder();
+
+ /**
+ * After the decoder has been started asynchronously, wait for
+ * the "START" command to finish. The decoder may not be
+ * initialized yet, i.e. there is no audio_format information
+ * yet.
+ *
+ * The player lock is not held.
+ */
+ bool WaitForDecoder();
+
+ /**
+ * Wrapper for MultipleOutputs::Open(). Upon failure, it
+ * pauses the player.
+ *
+ * @return true on success
+ */
+ bool OpenOutput();
+
+ /**
+ * Obtains the next chunk from the music pipe, optionally applies
+ * cross-fading, and sends it to all audio outputs.
+ *
+ * @return true on success, false on error (playback will be stopped)
+ */
+ bool PlayNextChunk();
+
+ /**
+ * Sends a chunk of silence to the audio outputs. This is
+ * called when there is not enough decoded data in the pipe
+ * yet, to prevent underruns in the hardware buffers.
+ *
+ * The player lock is not held.
+ */
+ bool SendSilence();
+
+ /**
+ * Player lock must be held before calling.
+ */
+ void ProcessCommand();
+
+ /**
+ * This is called at the border between two songs: the audio output
+ * has consumed all chunks of the current song, and we should start
+ * sending chunks from the next one.
+ *
+ * The player lock is not held.
+ *
+ * @return true on success, false on error (playback will be stopped)
+ */
+ bool SongBorder();
+
+public:
+ /*
+ * The main loop of the player thread, during playback. This
+ * is basically a state machine, which multiplexes data
+ * between the decoder thread and the output threads.
+ */
+ void Run();
+};
+
+static void
+player_command_finished(PlayerControl &pc)
+{
+ pc.Lock();
+ pc.CommandFinished();
+ pc.Unlock();
+}
+
+void
+Player::StartDecoder(MusicPipe &_pipe)
+{
+ assert(queued || pc.command == PlayerCommand::SEEK);
+ assert(pc.next_song != nullptr);
+
+ SongTime start_time = pc.next_song->GetStartTime();
+ if (pc.command == PlayerCommand::SEEK)
+ start_time += pc.seek_time;
+
+ dc.Start(new DetachedSong(*pc.next_song),
+ start_time, pc.next_song->GetEndTime(),
+ buffer, _pipe);
+}
+
+void
+Player::StopDecoder()
+{
+ dc.Stop();
+
+ if (dc.pipe != nullptr) {
+ /* clear and free the decoder pipe */
+
+ dc.pipe->Clear(buffer);
+
+ if (dc.pipe != pipe)
+ delete dc.pipe;
+
+ dc.pipe = nullptr;
+ }
+}
+
+bool
+Player::WaitForDecoder()
+{
+ assert(queued || pc.command == PlayerCommand::SEEK);
+ assert(pc.next_song != nullptr);
+
+ queued = false;
+
+ pc.Lock();
+ Error error = dc.GetError();
+ if (error.IsDefined()) {
+ pc.SetError(PlayerError::DECODER, std::move(error));
+
+ delete pc.next_song;
+ pc.next_song = nullptr;
+
+ pc.Unlock();
+
+ return false;
+ }
+
+ pc.ClearTaggedSong();
+
+ delete song;
+ song = pc.next_song;
+ elapsed_time = SongTime::zero();
+
+ /* set the "starting" flag, which will be cleared by
+ player_check_decoder_startup() */
+ decoder_starting = true;
+
+ /* update PlayerControl's song information */
+ pc.total_time = pc.next_song->GetDuration();
+ pc.bit_rate = 0;
+ pc.audio_format.Clear();
+
+ /* clear the queued song */
+ pc.next_song = nullptr;
+
+ pc.Unlock();
+
+ /* call syncPlaylistWithQueue() in the main thread */
+ pc.listener.OnPlayerSync();
+
+ return true;
+}
+
+/**
+ * Returns the real duration of the song, comprising the duration
+ * indicated by the decoder plugin.
+ */
+static SignedSongTime
+real_song_duration(const DetachedSong &song, SignedSongTime decoder_duration)
+{
+ if (decoder_duration.IsNegative())
+ /* the decoder plugin didn't provide information; fall
+ back to Song::GetDuration() */
+ return song.GetDuration();
+
+ const SongTime start_time = song.GetStartTime();
+ const SongTime end_time = song.GetEndTime();
+
+ if (end_time.IsPositive() && end_time < SongTime(decoder_duration))
+ return SignedSongTime(end_time - start_time);
+
+ return SignedSongTime(SongTime(decoder_duration) - start_time);
+}
+
+bool
+Player::OpenOutput()
+{
+ assert(play_audio_format.IsDefined());
+ assert(pc.state == PlayerState::PLAY ||
+ pc.state == PlayerState::PAUSE);
+
+ Error error;
+ if (pc.outputs.Open(play_audio_format, buffer, error)) {
+ output_open = true;
+ paused = false;
+
+ pc.Lock();
+ pc.state = PlayerState::PLAY;
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return true;
+ } else {
+ LogError(error);
+
+ output_open = false;
+
+ /* pause: the user may resume playback as soon as an
+ audio output becomes available */
+ paused = true;
+
+ pc.Lock();
+ pc.SetError(PlayerError::OUTPUT, std::move(error));
+ pc.state = PlayerState::PAUSE;
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return false;
+ }
+}
+
+bool
+Player::CheckDecoderStartup()
+{
+ assert(decoder_starting);
+
+ pc.Lock();
+
+ Error error = dc.GetError();
+ if (error.IsDefined()) {
+ /* the decoder failed */
+ pc.SetError(PlayerError::DECODER, std::move(error));
+ pc.Unlock();
+
+ return false;
+ } else if (!dc.IsStarting()) {
+ /* the decoder is ready and ok */
+
+ pc.Unlock();
+
+ if (output_open &&
+ !pc.outputs.Wait(pc, 1))
+ /* the output devices havn't finished playing
+ all chunks yet - wait for that */
+ return true;
+
+ pc.Lock();
+ pc.total_time = real_song_duration(*dc.song, dc.total_time);
+ pc.audio_format = dc.in_audio_format;
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ play_audio_format = dc.out_audio_format;
+ decoder_starting = false;
+
+ if (!paused && !OpenOutput()) {
+ FormatError(player_domain,
+ "problems opening audio device "
+ "while playing \"%s\"",
+ dc.song->GetURI());
+ return true;
+ }
+
+ return true;
+ } else {
+ /* the decoder is not yet ready; wait
+ some more */
+ dc.WaitForDecoder();
+ pc.Unlock();
+
+ return true;
+ }
+}
+
+bool
+Player::SendSilence()
+{
+ assert(output_open);
+ assert(play_audio_format.IsDefined());
+
+ MusicChunk *chunk = buffer.Allocate();
+ if (chunk == nullptr) {
+ LogError(player_domain, "Failed to allocate silence buffer");
+ return false;
+ }
+
+#ifndef NDEBUG
+ chunk->audio_format = play_audio_format;
+#endif
+
+ const size_t frame_size = play_audio_format.GetFrameSize();
+ /* this formula ensures that we don't send
+ partial frames */
+ unsigned num_frames = sizeof(chunk->data) / frame_size;
+
+ chunk->time = SignedSongTime::Negative(); /* undefined time stamp */
+ chunk->length = num_frames * frame_size;
+ memset(chunk->data, 0, chunk->length);
+
+ Error error;
+ if (!pc.outputs.Play(chunk, error)) {
+ LogError(error);
+ buffer.Return(chunk);
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+Player::SeekDecoder()
+{
+ assert(pc.next_song != nullptr);
+
+ const SongTime start_time = pc.next_song->GetStartTime();
+
+ if (!dc.LockIsCurrentSong(*pc.next_song)) {
+ /* the decoder is already decoding the "next" song -
+ stop it and start the previous song again */
+
+ StopDecoder();
+
+ /* clear music chunks which might still reside in the
+ pipe */
+ pipe->Clear(buffer);
+
+ /* re-start the decoder */
+ StartDecoder(*pipe);
+ if (!WaitForDecoder()) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+ } else {
+ if (!IsDecoderAtCurrentSong()) {
+ /* the decoder is already decoding the "next" song,
+ but it is the same song file; exchange the pipe */
+ ClearAndReplacePipe(dc.pipe);
+ }
+
+ delete pc.next_song;
+ pc.next_song = nullptr;
+ queued = false;
+ }
+
+ /* wait for the decoder to complete initialization */
+
+ while (decoder_starting) {
+ if (!CheckDecoderStartup()) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+ }
+
+ /* send the SEEK command */
+
+ SongTime where = pc.seek_time;
+ if (!pc.total_time.IsNegative()) {
+ const SongTime total_time(pc.total_time);
+ if (where > total_time)
+ where = total_time;
+ }
+
+ if (!dc.Seek(where + start_time)) {
+ /* decoder failure */
+ player_command_finished(pc);
+ return false;
+ }
+
+ elapsed_time = where;
+
+ player_command_finished(pc);
+
+ xfade_state = CrossFadeState::UNKNOWN;
+
+ /* re-fill the buffer after seeking */
+ buffering = true;
+
+ pc.outputs.Cancel();
+
+ return true;
+}
+
+inline void
+Player::ProcessCommand()
+{
+ switch (pc.command) {
+ case PlayerCommand::NONE:
+ case PlayerCommand::STOP:
+ case PlayerCommand::EXIT:
+ case PlayerCommand::CLOSE_AUDIO:
+ break;
+
+ case PlayerCommand::UPDATE_AUDIO:
+ pc.Unlock();
+ pc.outputs.EnableDisable();
+ pc.Lock();
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::QUEUE:
+ assert(pc.next_song != nullptr);
+ assert(!queued);
+ assert(!IsDecoderAtNextSong());
+
+ queued = true;
+ pc.CommandFinished();
+
+ pc.Unlock();
+ if (dc.LockIsIdle())
+ StartDecoder(*new MusicPipe());
+ pc.Lock();
+
+ break;
+
+ case PlayerCommand::PAUSE:
+ pc.Unlock();
+
+ paused = !paused;
+ if (paused) {
+ pc.outputs.Pause();
+ pc.Lock();
+
+ pc.state = PlayerState::PAUSE;
+ } else if (!play_audio_format.IsDefined()) {
+ /* the decoder hasn't provided an audio format
+ yet - don't open the audio device yet */
+ pc.Lock();
+
+ pc.state = PlayerState::PLAY;
+ } else {
+ OpenOutput();
+
+ pc.Lock();
+ }
+
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::SEEK:
+ pc.Unlock();
+ SeekDecoder();
+ pc.Lock();
+ break;
+
+ case PlayerCommand::CANCEL:
+ if (pc.next_song == nullptr) {
+ /* the cancel request arrived too late, we're
+ already playing the queued song... stop
+ everything now */
+ pc.command = PlayerCommand::STOP;
+ return;
+ }
+
+ if (IsDecoderAtNextSong()) {
+ /* the decoder is already decoding the song -
+ stop it and reset the position */
+ pc.Unlock();
+ StopDecoder();
+ pc.Lock();
+ }
+
+ delete pc.next_song;
+ pc.next_song = nullptr;
+ queued = false;
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::REFRESH:
+ if (output_open && !paused) {
+ pc.Unlock();
+ pc.outputs.Check();
+ pc.Lock();
+ }
+
+ pc.elapsed_time = !pc.outputs.GetElapsedTime().IsNegative()
+ ? SongTime(pc.outputs.GetElapsedTime())
+ : elapsed_time;
+
+ pc.CommandFinished();
+ break;
+ }
+}
+
+static void
+update_song_tag(PlayerControl &pc, DetachedSong &song, const Tag &new_tag)
+{
+ if (song.IsFile())
+ /* don't update tags of local files, only remote
+ streams may change tags dynamically */
+ return;
+
+ song.SetTag(new_tag);
+
+ pc.LockSetTaggedSong(song);
+
+ /* the main thread will update the playlist version when he
+ receives this event */
+ pc.listener.OnPlayerTagModified();
+
+ /* notify all clients that the tag of the current song has
+ changed */
+ idle_add(IDLE_PLAYER);
+}
+
+/**
+ * Plays a #MusicChunk object (after applying software volume). If
+ * it contains a (stream) tag, copy it to the current song, so MPD's
+ * playlist reflects the new stream tag.
+ *
+ * Player lock is not held.
+ */
+static bool
+play_chunk(PlayerControl &pc,
+ DetachedSong &song, MusicChunk *chunk,
+ MusicBuffer &buffer,
+ const AudioFormat format,
+ Error &error)
+{
+ assert(chunk->CheckFormat(format));
+
+ if (chunk->tag != nullptr)
+ update_song_tag(pc, song, *chunk->tag);
+
+ if (chunk->IsEmpty()) {
+ buffer.Return(chunk);
+ return true;
+ }
+
+ pc.Lock();
+ pc.bit_rate = chunk->bit_rate;
+ pc.Unlock();
+
+ /* send the chunk to the audio outputs */
+
+ if (!pc.outputs.Play(chunk, error))
+ return false;
+
+ pc.total_play_time += (double)chunk->length /
+ format.GetTimeToSize();
+ return true;
+}
+
+inline bool
+Player::PlayNextChunk()
+{
+ if (!pc.outputs.Wait(pc, 64))
+ /* the output pipe is still large enough, don't send
+ another chunk */
+ return true;
+
+ unsigned cross_fade_position;
+ MusicChunk *chunk = nullptr;
+ if (xfade_state == CrossFadeState::ENABLED && IsDecoderAtNextSong() &&
+ (cross_fade_position = pipe->GetSize()) <= cross_fade_chunks) {
+ /* perform cross fade */
+ MusicChunk *other_chunk = dc.pipe->Shift();
+
+ if (!cross_fading) {
+ /* beginning of the cross fade - adjust
+ crossFadeChunks which might be bigger than
+ the remaining number of chunks in the old
+ song */
+ cross_fade_chunks = cross_fade_position;
+ cross_fading = true;
+ }
+
+ if (other_chunk != nullptr) {
+ chunk = pipe->Shift();
+ assert(chunk != nullptr);
+ assert(chunk->other == nullptr);
+
+ /* don't send the tags of the new song (which
+ is being faded in) yet; postpone it until
+ the current song is faded out */
+ cross_fade_tag =
+ Tag::MergeReplace(cross_fade_tag,
+ other_chunk->tag);
+ other_chunk->tag = nullptr;
+
+ if (pc.cross_fade.mixramp_delay <= 0) {
+ chunk->mix_ratio = ((float)cross_fade_position)
+ / cross_fade_chunks;
+ } else {
+ chunk->mix_ratio = -1;
+ }
+
+ if (other_chunk->IsEmpty()) {
+ /* the "other" chunk was a MusicChunk
+ which had only a tag, but no music
+ data - we cannot cross-fade that;
+ but since this happens only at the
+ beginning of the new song, we can
+ easily recover by throwing it away
+ now */
+ buffer.Return(other_chunk);
+ other_chunk = nullptr;
+ }
+
+ chunk->other = other_chunk;
+ } else {
+ /* there are not enough decoded chunks yet */
+
+ pc.Lock();
+
+ if (dc.IsIdle()) {
+ /* the decoder isn't running, abort
+ cross fading */
+ pc.Unlock();
+
+ xfade_state = CrossFadeState::DISABLED;
+ } else {
+ /* wait for the decoder */
+ dc.Signal();
+ dc.WaitForDecoder();
+ pc.Unlock();
+
+ return true;
+ }
+ }
+ }
+
+ if (chunk == nullptr)
+ chunk = pipe->Shift();
+
+ assert(chunk != nullptr);
+
+ /* insert the postponed tag if cross-fading is finished */
+
+ if (xfade_state != CrossFadeState::ENABLED && cross_fade_tag != nullptr) {
+ chunk->tag = Tag::MergeReplace(chunk->tag, cross_fade_tag);
+ cross_fade_tag = nullptr;
+ }
+
+ /* play the current chunk */
+
+ Error error;
+ if (!play_chunk(pc, *song, chunk, buffer, play_audio_format, error)) {
+ LogError(error);
+
+ buffer.Return(chunk);
+
+ pc.Lock();
+
+ pc.SetError(PlayerError::OUTPUT, std::move(error));
+
+ /* pause: the user may resume playback as soon as an
+ audio output becomes available */
+ pc.state = PlayerState::PAUSE;
+ paused = true;
+
+ pc.Unlock();
+
+ idle_add(IDLE_PLAYER);
+
+ return false;
+ }
+
+ /* this formula should prevent that the decoder gets woken up
+ with each chunk; it is more efficient to make it decode a
+ larger block at a time */
+ pc.Lock();
+ if (!dc.IsIdle() &&
+ dc.pipe->GetSize() <= (pc.buffered_before_play +
+ buffer.GetSize() * 3) / 4) {
+ if (!decoder_woken) {
+ decoder_woken = true;
+ dc.Signal();
+ }
+ } else
+ decoder_woken = false;
+ pc.Unlock();
+
+ return true;
+}
+
+inline bool
+Player::SongBorder()
+{
+ xfade_state = CrossFadeState::UNKNOWN;
+
+ FormatDefault(player_domain, "played \"%s\"", song->GetURI());
+
+ ReplacePipe(dc.pipe);
+
+ pc.outputs.SongBorder();
+
+ if (!WaitForDecoder())
+ return false;
+
+ pc.Lock();
+
+ const bool border_pause = pc.border_pause;
+ if (border_pause) {
+ paused = true;
+ pc.state = PlayerState::PAUSE;
+ }
+
+ pc.Unlock();
+
+ if (border_pause)
+ idle_add(IDLE_PLAYER);
+
+ return true;
+}
+
+inline void
+Player::Run()
+{
+ pipe = new MusicPipe();
+
+ StartDecoder(*pipe);
+ if (!WaitForDecoder()) {
+ assert(song == nullptr);
+
+ StopDecoder();
+ player_command_finished(pc);
+ delete pipe;
+ return;
+ }
+
+ pc.Lock();
+ pc.state = PlayerState::PLAY;
+
+ if (pc.command == PlayerCommand::SEEK)
+ elapsed_time = pc.seek_time;
+
+ pc.CommandFinished();
+
+ while (true) {
+ ProcessCommand();
+ if (pc.command == PlayerCommand::STOP ||
+ pc.command == PlayerCommand::EXIT ||
+ pc.command == PlayerCommand::CLOSE_AUDIO) {
+ pc.Unlock();
+ pc.outputs.Cancel();
+ break;
+ }
+
+ pc.Unlock();
+
+ if (buffering) {
+ /* buffering at the start of the song - wait
+ until the buffer is large enough, to
+ prevent stuttering on slow machines */
+
+ if (pipe->GetSize() < pc.buffered_before_play &&
+ !dc.LockIsIdle()) {
+ /* not enough decoded buffer space yet */
+
+ if (!paused && output_open &&
+ pc.outputs.Check() < 4 &&
+ !SendSilence())
+ break;
+
+ pc.Lock();
+ /* XXX race condition: check decoder again */
+ dc.WaitForDecoder();
+ continue;
+ } else {
+ /* buffering is complete */
+ buffering = false;
+ }
+ }
+
+ if (decoder_starting) {
+ /* wait until the decoder is initialized completely */
+
+ if (!CheckDecoderStartup())
+ break;
+
+ pc.Lock();
+ continue;
+ }
+
+#ifndef NDEBUG
+ /*
+ music_pipe_check_format(&play_audio_format,
+ next_song_chunk,
+ &dc.out_audio_format);
+ */
+#endif
+
+ if (dc.LockIsIdle() && queued && dc.pipe == pipe) {
+ /* the decoder has finished the current song;
+ make it decode the next song */
+
+ assert(dc.pipe == nullptr || dc.pipe == pipe);
+
+ StartDecoder(*new MusicPipe());
+ }
+
+ if (/* no cross-fading if MPD is going to pause at the
+ end of the current song */
+ !pc.border_pause &&
+ IsDecoderAtNextSong() &&
+ xfade_state == CrossFadeState::UNKNOWN &&
+ !dc.LockIsStarting()) {
+ /* enable cross fading in this song? if yes,
+ calculate how many chunks will be required
+ for it */
+ cross_fade_chunks =
+ pc.cross_fade.Calculate(dc.total_time,
+ dc.replay_gain_db,
+ dc.replay_gain_prev_db,
+ dc.GetMixRampStart(),
+ dc.GetMixRampPreviousEnd(),
+ dc.out_audio_format,
+ play_audio_format,
+ buffer.GetSize() -
+ pc.buffered_before_play);
+ if (cross_fade_chunks > 0) {
+ xfade_state = CrossFadeState::ENABLED;
+ cross_fading = false;
+ } else
+ /* cross fading is disabled or the
+ next song is too short */
+ xfade_state = CrossFadeState::DISABLED;
+ }
+
+ if (paused) {
+ pc.Lock();
+
+ if (pc.command == PlayerCommand::NONE)
+ pc.Wait();
+ continue;
+ } else if (!pipe->IsEmpty()) {
+ /* at least one music chunk is ready - send it
+ to the audio output */
+
+ PlayNextChunk();
+ } else if (pc.outputs.Check() > 0) {
+ /* not enough data from decoder, but the
+ output thread is still busy, so it's
+ okay */
+
+ pc.Lock();
+
+ /* wake up the decoder (just in case it's
+ waiting for space in the MusicBuffer) and
+ wait for it */
+ dc.Signal();
+ dc.WaitForDecoder();
+ continue;
+ } else if (IsDecoderAtNextSong()) {
+ /* at the beginning of a new song */
+
+ if (!SongBorder())
+ break;
+ } else if (dc.LockIsIdle()) {
+ /* check the size of the pipe again, because
+ the decoder thread may have added something
+ since we last checked */
+ if (pipe->IsEmpty()) {
+ /* wait for the hardware to finish
+ playback */
+ pc.outputs.Drain();
+ break;
+ }
+ } else if (output_open) {
+ /* the decoder is too busy and hasn't provided
+ new PCM data in time: send silence (if the
+ output pipe is empty) */
+ if (!SendSilence())
+ break;
+ }
+
+ pc.Lock();
+ }
+
+ StopDecoder();
+
+ ClearAndDeletePipe();
+
+ delete cross_fade_tag;
+
+ if (song != nullptr) {
+ FormatDefault(player_domain, "played \"%s\"", song->GetURI());
+ delete song;
+ }
+
+ pc.Lock();
+
+ pc.ClearTaggedSong();
+
+ if (queued) {
+ assert(pc.next_song != nullptr);
+ delete pc.next_song;
+ pc.next_song = nullptr;
+ }
+
+ pc.state = PlayerState::STOP;
+
+ pc.Unlock();
+}
+
+static void
+do_play(PlayerControl &pc, DecoderControl &dc,
+ MusicBuffer &buffer)
+{
+ Player player(pc, dc, buffer);
+ player.Run();
+}
+
+static void
+player_task(void *arg)
+{
+ PlayerControl &pc = *(PlayerControl *)arg;
+
+ SetThreadName("player");
+
+ DecoderControl dc(pc.mutex, pc.cond);
+ decoder_thread_start(dc);
+
+ MusicBuffer buffer(pc.buffer_chunks);
+
+ pc.Lock();
+
+ while (1) {
+ switch (pc.command) {
+ case PlayerCommand::SEEK:
+ case PlayerCommand::QUEUE:
+ assert(pc.next_song != nullptr);
+
+ pc.Unlock();
+ do_play(pc, dc, buffer);
+ pc.listener.OnPlayerSync();
+ pc.Lock();
+ break;
+
+ case PlayerCommand::STOP:
+ pc.Unlock();
+ pc.outputs.Cancel();
+ pc.Lock();
+
+ /* fall through */
+
+ case PlayerCommand::PAUSE:
+ delete pc.next_song;
+ pc.next_song = nullptr;
+
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::CLOSE_AUDIO:
+ pc.Unlock();
+
+ pc.outputs.Release();
+
+ pc.Lock();
+ pc.CommandFinished();
+
+ assert(buffer.IsEmptyUnsafe());
+
+ break;
+
+ case PlayerCommand::UPDATE_AUDIO:
+ pc.Unlock();
+ pc.outputs.EnableDisable();
+ pc.Lock();
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::EXIT:
+ pc.Unlock();
+
+ dc.Quit();
+
+ pc.outputs.Close();
+
+ player_command_finished(pc);
+ return;
+
+ case PlayerCommand::CANCEL:
+ delete pc.next_song;
+ pc.next_song = nullptr;
+
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::REFRESH:
+ /* no-op when not playing */
+ pc.CommandFinished();
+ break;
+
+ case PlayerCommand::NONE:
+ pc.Wait();
+ break;
+ }
+ }
+}
+
+void
+StartPlayerThread(PlayerControl &pc)
+{
+ assert(!pc.thread.IsDefined());
+
+ Error error;
+ if (!pc.thread.Start(player_task, &pc, error))
+ FatalError(error);
+}
diff --git a/src/player/Thread.hxx b/src/player/Thread.hxx
new file mode 100644
index 000000000..fc6ea4364
--- /dev/null
+++ b/src/player/Thread.hxx
@@ -0,0 +1,45 @@
+/*
+ * 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
+ * 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.
+ */
+
+/* \file
+ *
+ * The player thread controls the playback. It acts as a bridge
+ * between the decoder thread and the output thread(s): it receives
+ * #MusicChunk objects from the decoder, optionally mixes them
+ * (cross-fading), applies software volume, and sends them to the
+ * audio outputs via audio_output_all_play().
+ *
+ * It is controlled by the main thread (the playlist code), see
+ * Control.hxx. The playlist enqueues new songs into the player
+ * thread and sends it commands.
+ *
+ * The player thread itself does not do any I/O. It synchronizes with
+ * other threads via #GMutex and #GCond objects, and passes
+ * #MusicChunk instances around in #MusicPipe objects.
+ */
+
+#ifndef MPD_PLAYER_THREAD_HXX
+#define MPD_PLAYER_THREAD_HXX
+
+struct PlayerControl;
+
+void
+StartPlayerThread(PlayerControl &pc);
+
+#endif