From debdf9bb9674968fa047ec4f6c6f5cd417c3d007 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 21 Sep 2018 16:56:53 +0200 Subject: decoder/{Thread,Control,...}: rename source files, drop prefix --- src/decoder/Bridge.cxx | 4 +- src/decoder/Client.hxx | 2 +- src/decoder/Command.hxx | 32 +++ src/decoder/Control.cxx | 171 +++++++++++++ src/decoder/Control.hxx | 446 ++++++++++++++++++++++++++++++++ src/decoder/DecoderAPI.hxx | 2 +- src/decoder/DecoderCommand.hxx | 32 --- src/decoder/DecoderControl.cxx | 172 ------------- src/decoder/DecoderControl.hxx | 446 -------------------------------- src/decoder/DecoderError.cxx | 23 -- src/decoder/DecoderError.hxx | 25 -- src/decoder/DecoderThread.cxx | 566 ----------------------------------------- src/decoder/Domain.cxx | 23 ++ src/decoder/Domain.hxx | 25 ++ src/decoder/Thread.cxx | 565 ++++++++++++++++++++++++++++++++++++++++ src/player/Thread.cxx | 2 +- 16 files changed, 1267 insertions(+), 1269 deletions(-) create mode 100644 src/decoder/Command.hxx create mode 100644 src/decoder/Control.cxx create mode 100644 src/decoder/Control.hxx delete mode 100644 src/decoder/DecoderCommand.hxx delete mode 100644 src/decoder/DecoderControl.cxx delete mode 100644 src/decoder/DecoderControl.hxx delete mode 100644 src/decoder/DecoderError.cxx delete mode 100644 src/decoder/DecoderError.hxx delete mode 100644 src/decoder/DecoderThread.cxx create mode 100644 src/decoder/Domain.cxx create mode 100644 src/decoder/Domain.hxx create mode 100644 src/decoder/Thread.cxx (limited to 'src') diff --git a/src/decoder/Bridge.cxx b/src/decoder/Bridge.cxx index d5585585b..06e02d24b 100644 --- a/src/decoder/Bridge.cxx +++ b/src/decoder/Bridge.cxx @@ -20,8 +20,8 @@ #include "config.h" #include "Bridge.hxx" #include "DecoderAPI.hxx" -#include "DecoderError.hxx" -#include "DecoderControl.hxx" +#include "Domain.hxx" +#include "Control.hxx" #include "song/DetachedSong.hxx" #include "pcm/PcmConvert.hxx" #include "MusicPipe.hxx" diff --git a/src/decoder/Client.hxx b/src/decoder/Client.hxx index 5abf43381..f8f190fda 100644 --- a/src/decoder/Client.hxx +++ b/src/decoder/Client.hxx @@ -21,7 +21,7 @@ #define MPD_DECODER_CLIENT_HXX #include "check.h" -#include "DecoderCommand.hxx" +#include "Command.hxx" #include "Chrono.hxx" #include "input/Ptr.hxx" #include "util/Compiler.h" diff --git a/src/decoder/Command.hxx b/src/decoder/Command.hxx new file mode 100644 index 000000000..c2425937d --- /dev/null +++ b/src/decoder/Command.hxx @@ -0,0 +1,32 @@ +/* + * Copyright 2003-2017 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_DECODER_COMMAND_HXX +#define MPD_DECODER_COMMAND_HXX + +#include + +enum class DecoderCommand : uint8_t { + NONE = 0, + START, + STOP, + SEEK +}; + +#endif diff --git a/src/decoder/Control.cxx b/src/decoder/Control.cxx new file mode 100644 index 000000000..5cf0d1d7f --- /dev/null +++ b/src/decoder/Control.cxx @@ -0,0 +1,171 @@ +/* + * Copyright 2003-2017 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 "MusicPipe.hxx" +#include "song/DetachedSong.hxx" + +#include + +#include + +DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond, + const AudioFormat _configured_audio_format, + const ReplayGainConfig &_replay_gain_config) noexcept + :thread(BIND_THIS_METHOD(RunThread)), + mutex(_mutex), client_cond(_client_cond), + configured_audio_format(_configured_audio_format), + replay_gain_config(_replay_gain_config) {} + +DecoderControl::~DecoderControl() noexcept +{ + ClearError(); +} + +void +DecoderControl::WaitForDecoder() noexcept +{ + assert(!client_is_waiting); + client_is_waiting = true; + + client_cond.wait(mutex); + + assert(client_is_waiting); + client_is_waiting = false; +} + +void +DecoderControl::SetReady(const AudioFormat audio_format, + bool _seekable, SignedSongTime _duration) noexcept +{ + assert(state == DecoderState::START); + assert(pipe != nullptr); + assert(pipe->IsEmpty()); + assert(audio_format.IsDefined()); + assert(audio_format.IsValid()); + + in_audio_format = audio_format; + out_audio_format = audio_format.WithMask(configured_audio_format); + + seekable = _seekable; + total_time = _duration; + + state = DecoderState::DECODE; + client_cond.signal(); +} + +bool +DecoderControl::IsCurrentSong(const DetachedSong &_song) const noexcept +{ + switch (state) { + case DecoderState::STOP: + case DecoderState::ERROR: + return false; + + case DecoderState::START: + case DecoderState::DECODE: + return song->IsSame(_song); + } + + assert(false); + gcc_unreachable(); +} + +void +DecoderControl::Start(std::unique_ptr _song, + SongTime _start_time, SongTime _end_time, + MusicBuffer &_buffer, + std::shared_ptr _pipe) noexcept +{ + assert(_song != nullptr); + assert(_pipe->IsEmpty()); + + song = std::move(_song); + start_time = _start_time; + end_time = _end_time; + buffer = &_buffer; + pipe = std::move(_pipe); + + ClearError(); + SynchronousCommandLocked(DecoderCommand::START); +} + +void +DecoderControl::Stop() noexcept +{ + if (command != DecoderCommand::NONE) + /* Attempt to cancel the current command. If it's too + late and the decoder thread is already executing + the old command, we'll call STOP again in this + function (see below). */ + SynchronousCommandLocked(DecoderCommand::STOP); + + if (state != DecoderState::STOP && state != DecoderState::ERROR) + SynchronousCommandLocked(DecoderCommand::STOP); +} + +void +DecoderControl::Seek(SongTime t) +{ + assert(state != DecoderState::START); + assert(state != DecoderState::ERROR); + + switch (state) { + case DecoderState::START: + case DecoderState::ERROR: + gcc_unreachable(); + + case DecoderState::STOP: + /* TODO: if this happens, the caller should be given a + chance to restart the decoder */ + throw std::runtime_error("Decoder is dead"); + + case DecoderState::DECODE: + break; + } + + if (!seekable) + throw std::runtime_error("Not seekable"); + + seek_time = t; + seek_error = false; + SynchronousCommandLocked(DecoderCommand::SEEK); + + if (seek_error) + throw std::runtime_error("Decoder failed to seek"); +} + +void +DecoderControl::Quit() noexcept +{ + assert(thread.IsDefined()); + + quit = true; + LockAsynchronousCommand(DecoderCommand::STOP); + + thread.Join(); +} + +void +DecoderControl::CycleMixRamp() noexcept +{ + previous_mix_ramp = std::move(mix_ramp); + mix_ramp.Clear(); +} diff --git a/src/decoder/Control.hxx b/src/decoder/Control.hxx new file mode 100644 index 000000000..6c1a7183e --- /dev/null +++ b/src/decoder/Control.hxx @@ -0,0 +1,446 @@ +/* + * Copyright 2003-2017 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_DECODER_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX + +#include "Command.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "input/Handler.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "Chrono.hxx" +#include "ReplayGainConfig.hxx" +#include "ReplayGainMode.hxx" + +#include +#include +#include + +#include +#include + +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif + +class DetachedSong; +class MusicBuffer; +class MusicPipe; + +enum class DecoderState : uint8_t { + STOP = 0, + START, + DECODE, + + /** + * The last "START" command failed, because there was an I/O + * error or because no decoder was able to decode the file. + * This state will only come after START; once the state has + * turned to DECODE, by definition no such error can occur. + */ + ERROR, +}; + +struct DecoderControl final : InputStreamHandler { + /** + * The handle of the decoder thread. + */ + Thread thread; + + /** + * This lock protects #state and #command. + * + * This is usually a reference to PlayerControl::mutex, so + * that both player thread and decoder thread share a mutex. + * This simplifies synchronization with #cond and + * #client_cond. + */ + Mutex &mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + Cond cond; + + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + * + * This is usually a reference to PlayerControl::cond. + */ + Cond &client_cond; + + DecoderState state = DecoderState::STOP; + DecoderCommand command = DecoderCommand::NONE; + + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DecoderState::ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DecoderState::START). + */ + std::exception_ptr error; + + bool quit; + + /** + * Is the client currently waiting for the DecoderThread? If + * false, the DecoderThread may omit invoking Cond::signal(), + * reducing the number of system calls. + */ + bool client_is_waiting = false; + + bool seek_error; + bool seekable; + SongTime seek_time; + + /** + * The "audio_output_format" setting. + */ + const AudioFormat configured_audio_format; + + /** the format of the song file */ + AudioFormat in_audio_format; + + /** the format being sent to the music pipe */ + AudioFormat out_audio_format; + + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DecoderCommand::START + * command. + */ + std::unique_ptr song; + + /** + * The initial seek position, e.g. to the start of a sub-track + * described by a CUE file. + * + * This attribute is set by Start(). + */ + SongTime start_time; + + /** + * The decoder will stop when it reaches this position. 0 + * means don't stop before the end of the file. + * + * This attribute is set by Start(). + */ + SongTime end_time; + + SignedSongTime total_time; + + /** the #MusicChunk allocator */ + MusicBuffer *buffer; + + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ + std::shared_ptr pipe; + + const ReplayGainConfig replay_gain_config; + ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; + + float replay_gain_db = 0; + float replay_gain_prev_db = 0; + + MixRampInfo mix_ramp, previous_mix_ramp; + + /** + * @param _mutex see #mutex + * @param _client_cond see #client_cond + */ + DecoderControl(Mutex &_mutex, Cond &_client_cond, + const AudioFormat _configured_audio_format, + const ReplayGainConfig &_replay_gain_config) noexcept; + ~DecoderControl() noexcept; + + /** + * Throws on error. + */ + void StartThread() { + quit = false; + thread.Start(); + } + + /** + * Locks the object. + */ + void Lock() const noexcept { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const noexcept { + mutex.unlock(); + } + + /** + * Signals the object. This function is only valid in the + * player thread. The object should be locked prior to + * calling this function. + */ + void Signal() noexcept { + cond.signal(); + } + + /** + * Waits for a signal on the #DecoderControl object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ + void Wait() noexcept { + cond.wait(mutex); + } + + /** + * Waits for a signal from the decoder thread. This object + * must be locked prior to calling this function. This method + * is only valid in the player thread. + * + * Caller must hold the lock. + */ + void WaitForDecoder() noexcept; + + bool IsIdle() const noexcept { + return state == DecoderState::STOP || + state == DecoderState::ERROR; + } + + gcc_pure + bool LockIsIdle() const noexcept { + const std::lock_guard protect(mutex); + return IsIdle(); + } + + bool IsStarting() const noexcept { + return state == DecoderState::START; + } + + gcc_pure + bool LockIsStarting() const noexcept { + const std::lock_guard protect(mutex); + return IsStarting(); + } + + bool HasFailed() const noexcept { + assert(command == DecoderCommand::NONE); + + return state == DecoderState::ERROR; + } + + gcc_pure + bool LockHasFailed() const noexcept { + const std::lock_guard protect(mutex); + return HasFailed(); + } + + /** + * Transition this obejct from DecoderState::START to + * DecoderState::DECODE. + * + * Caller must lock the object. + */ + void SetReady(const AudioFormat audio_format, + bool _seekable, SignedSongTime _duration) noexcept; + + /** + * Checks whether an error has occurred, and if so, rethrows + * it. + * + * Caller must lock the object. + */ + void CheckRethrowError() const { + assert(command == DecoderCommand::NONE); + assert(state != DecoderState::ERROR || error); + + if (state == DecoderState::ERROR) + std::rethrow_exception(error); + } + + /** + * Like CheckRethrowError(), but locks and unlocks the object. + */ + void LockCheckRethrowError() const { + const std::lock_guard protect(mutex); + CheckRethrowError(); + } + + /** + * Clear the error condition (if any). + * + * Caller must lock the object. + */ + void ClearError() noexcept { + if (state == DecoderState::ERROR) { + error = std::exception_ptr(); + state = DecoderState::STOP; + } + } + + /** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ + gcc_pure + bool IsCurrentSong(const DetachedSong &_song) const noexcept; + + gcc_pure + bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept { + return seekable && IsCurrentSong(_song); + } + +private: + /** + * Wait for the command to be finished by the decoder thread. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void WaitCommandLocked() noexcept { + while (command != DecoderCommand::NONE) + WaitForDecoder(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void SynchronousCommandLocked(DecoderCommand cmd) noexcept { + command = cmd; + Signal(); + WaitCommandLocked(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. This method locks the + * object. + */ + void LockSynchronousCommand(DecoderCommand cmd) noexcept { + const std::lock_guard protect(mutex); + ClearError(); + SynchronousCommandLocked(cmd); + } + + void LockAsynchronousCommand(DecoderCommand cmd) noexcept { + const std::lock_guard protect(mutex); + command = cmd; + Signal(); + } + +public: + /** + * Marks the current command as "finished" and notifies the + * client (= player thread). + * + * To be called from the decoder thread. Caller must lock the + * mutex. + */ + void CommandFinishedLocked() noexcept { + assert(command != DecoderCommand::NONE); + + command = DecoderCommand::NONE; + client_cond.signal(); + } + + /** + * Start the decoder. + * + * Caller must lock the object. + * + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder + * @param start_time see #DecoderControl + * @param end_time see #DecoderControl + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ + void Start(std::unique_ptr song, + SongTime start_time, SongTime end_time, + MusicBuffer &buffer, + std::shared_ptr pipe) noexcept; + + /** + * Caller must lock the object. + */ + void Stop() noexcept; + + /** + * Throws #std::runtime_error on error. + * + * Caller must lock the object. + */ + void Seek(SongTime t); + + void Quit() noexcept; + + const char *GetMixRampStart() const noexcept { + return mix_ramp.GetStart(); + } + + const char *GetMixRampEnd() const noexcept { + return mix_ramp.GetEnd(); + } + + const char *GetMixRampPreviousEnd() const noexcept { + return previous_mix_ramp.GetEnd(); + } + + void SetMixRamp(MixRampInfo &&new_value) noexcept { + mix_ramp = std::move(new_value); + } + + /** + * Move mixramp_end to mixramp_prev_end and clear + * mixramp_start/mixramp_end. + */ + void CycleMixRamp() noexcept; + +private: + void RunThread() noexcept; + + /* virtual methods from class InputStreamHandler */ + void OnInputStreamReady() noexcept override { + cond.signal(); + } + + void OnInputStreamAvailable() noexcept override { + cond.signal(); + } +}; + +#endif diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx index 6759d4c58..dc43f64f3 100644 --- a/src/decoder/DecoderAPI.hxx +++ b/src/decoder/DecoderAPI.hxx @@ -32,7 +32,7 @@ #include "check.h" #include "Client.hxx" #include "input/Ptr.hxx" -#include "DecoderCommand.hxx" +#include "Command.hxx" #include "DecoderPlugin.hxx" #include "ReplayGainInfo.hxx" #include "tag/Tag.hxx" diff --git a/src/decoder/DecoderCommand.hxx b/src/decoder/DecoderCommand.hxx deleted file mode 100644 index c2425937d..000000000 --- a/src/decoder/DecoderCommand.hxx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2003-2017 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_DECODER_COMMAND_HXX -#define MPD_DECODER_COMMAND_HXX - -#include - -enum class DecoderCommand : uint8_t { - NONE = 0, - START, - STOP, - SEEK -}; - -#endif diff --git a/src/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx deleted file mode 100644 index 88c75e008..000000000 --- a/src/decoder/DecoderControl.cxx +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2003-2017 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 "DecoderControl.hxx" -#include "DecoderError.hxx" -#include "MusicPipe.hxx" -#include "song/DetachedSong.hxx" - -#include - -#include - -DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond, - const AudioFormat _configured_audio_format, - const ReplayGainConfig &_replay_gain_config) noexcept - :thread(BIND_THIS_METHOD(RunThread)), - mutex(_mutex), client_cond(_client_cond), - configured_audio_format(_configured_audio_format), - replay_gain_config(_replay_gain_config) {} - -DecoderControl::~DecoderControl() noexcept -{ - ClearError(); -} - -void -DecoderControl::WaitForDecoder() noexcept -{ - assert(!client_is_waiting); - client_is_waiting = true; - - client_cond.wait(mutex); - - assert(client_is_waiting); - client_is_waiting = false; -} - -void -DecoderControl::SetReady(const AudioFormat audio_format, - bool _seekable, SignedSongTime _duration) noexcept -{ - assert(state == DecoderState::START); - assert(pipe != nullptr); - assert(pipe->IsEmpty()); - assert(audio_format.IsDefined()); - assert(audio_format.IsValid()); - - in_audio_format = audio_format; - out_audio_format = audio_format.WithMask(configured_audio_format); - - seekable = _seekable; - total_time = _duration; - - state = DecoderState::DECODE; - client_cond.signal(); -} - -bool -DecoderControl::IsCurrentSong(const DetachedSong &_song) const noexcept -{ - switch (state) { - case DecoderState::STOP: - case DecoderState::ERROR: - return false; - - case DecoderState::START: - case DecoderState::DECODE: - return song->IsSame(_song); - } - - assert(false); - gcc_unreachable(); -} - -void -DecoderControl::Start(std::unique_ptr _song, - SongTime _start_time, SongTime _end_time, - MusicBuffer &_buffer, - std::shared_ptr _pipe) noexcept -{ - assert(_song != nullptr); - assert(_pipe->IsEmpty()); - - song = std::move(_song); - start_time = _start_time; - end_time = _end_time; - buffer = &_buffer; - pipe = std::move(_pipe); - - ClearError(); - SynchronousCommandLocked(DecoderCommand::START); -} - -void -DecoderControl::Stop() noexcept -{ - if (command != DecoderCommand::NONE) - /* Attempt to cancel the current command. If it's too - late and the decoder thread is already executing - the old command, we'll call STOP again in this - function (see below). */ - SynchronousCommandLocked(DecoderCommand::STOP); - - if (state != DecoderState::STOP && state != DecoderState::ERROR) - SynchronousCommandLocked(DecoderCommand::STOP); -} - -void -DecoderControl::Seek(SongTime t) -{ - assert(state != DecoderState::START); - assert(state != DecoderState::ERROR); - - switch (state) { - case DecoderState::START: - case DecoderState::ERROR: - gcc_unreachable(); - - case DecoderState::STOP: - /* TODO: if this happens, the caller should be given a - chance to restart the decoder */ - throw std::runtime_error("Decoder is dead"); - - case DecoderState::DECODE: - break; - } - - if (!seekable) - throw std::runtime_error("Not seekable"); - - seek_time = t; - seek_error = false; - SynchronousCommandLocked(DecoderCommand::SEEK); - - if (seek_error) - throw std::runtime_error("Decoder failed to seek"); -} - -void -DecoderControl::Quit() noexcept -{ - assert(thread.IsDefined()); - - quit = true; - LockAsynchronousCommand(DecoderCommand::STOP); - - thread.Join(); -} - -void -DecoderControl::CycleMixRamp() noexcept -{ - previous_mix_ramp = std::move(mix_ramp); - mix_ramp.Clear(); -} diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx deleted file mode 100644 index 190b2ab22..000000000 --- a/src/decoder/DecoderControl.hxx +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright 2003-2017 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_DECODER_CONTROL_HXX -#define MPD_DECODER_CONTROL_HXX - -#include "DecoderCommand.hxx" -#include "AudioFormat.hxx" -#include "MixRampInfo.hxx" -#include "input/Handler.hxx" -#include "thread/Mutex.hxx" -#include "thread/Cond.hxx" -#include "thread/Thread.hxx" -#include "Chrono.hxx" -#include "ReplayGainConfig.hxx" -#include "ReplayGainMode.hxx" - -#include -#include -#include - -#include -#include - -/* damn you, windows.h! */ -#ifdef ERROR -#undef ERROR -#endif - -class DetachedSong; -class MusicBuffer; -class MusicPipe; - -enum class DecoderState : uint8_t { - STOP = 0, - START, - DECODE, - - /** - * The last "START" command failed, because there was an I/O - * error or because no decoder was able to decode the file. - * This state will only come after START; once the state has - * turned to DECODE, by definition no such error can occur. - */ - ERROR, -}; - -struct DecoderControl final : InputStreamHandler { - /** - * The handle of the decoder thread. - */ - Thread thread; - - /** - * This lock protects #state and #command. - * - * This is usually a reference to PlayerControl::mutex, so - * that both player thread and decoder thread share a mutex. - * This simplifies synchronization with #cond and - * #client_cond. - */ - Mutex &mutex; - - /** - * Trigger this object after you have modified #command. This - * is also used by the decoder thread to notify the caller - * when it has finished a command. - */ - Cond cond; - - /** - * The trigger of this object's client. It is signalled - * whenever an event occurs. - * - * This is usually a reference to PlayerControl::cond. - */ - Cond &client_cond; - - DecoderState state = DecoderState::STOP; - DecoderCommand command = DecoderCommand::NONE; - - /** - * The error that occurred in the decoder thread. This - * attribute is only valid if #state is #DecoderState::ERROR. - * The object must be freed when this object transitions to - * any other state (usually #DecoderState::START). - */ - std::exception_ptr error; - - bool quit; - - /** - * Is the client currently waiting for the DecoderThread? If - * false, the DecoderThread may omit invoking Cond::signal(), - * reducing the number of system calls. - */ - bool client_is_waiting = false; - - bool seek_error; - bool seekable; - SongTime seek_time; - - /** - * The "audio_output_format" setting. - */ - const AudioFormat configured_audio_format; - - /** the format of the song file */ - AudioFormat in_audio_format; - - /** the format being sent to the music pipe */ - AudioFormat out_audio_format; - - /** - * The song currently being decoded. This attribute is set by - * the player thread, when it sends the #DecoderCommand::START - * command. - */ - std::unique_ptr song; - - /** - * The initial seek position, e.g. to the start of a sub-track - * described by a CUE file. - * - * This attribute is set by Start(). - */ - SongTime start_time; - - /** - * The decoder will stop when it reaches this position. 0 - * means don't stop before the end of the file. - * - * This attribute is set by Start(). - */ - SongTime end_time; - - SignedSongTime total_time; - - /** the #MusicChunk allocator */ - MusicBuffer *buffer; - - /** - * The destination pipe for decoded chunks. The caller thread - * owns this object, and is responsible for freeing it. - */ - std::shared_ptr pipe; - - const ReplayGainConfig replay_gain_config; - ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; - - float replay_gain_db = 0; - float replay_gain_prev_db = 0; - - MixRampInfo mix_ramp, previous_mix_ramp; - - /** - * @param _mutex see #mutex - * @param _client_cond see #client_cond - */ - DecoderControl(Mutex &_mutex, Cond &_client_cond, - const AudioFormat _configured_audio_format, - const ReplayGainConfig &_replay_gain_config) noexcept; - ~DecoderControl() noexcept; - - /** - * Throws on error. - */ - void StartThread() { - quit = false; - thread.Start(); - } - - /** - * Locks the object. - */ - void Lock() const noexcept { - mutex.lock(); - } - - /** - * Unlocks the object. - */ - void Unlock() const noexcept { - mutex.unlock(); - } - - /** - * Signals the object. This function is only valid in the - * player thread. The object should be locked prior to - * calling this function. - */ - void Signal() noexcept { - cond.signal(); - } - - /** - * Waits for a signal on the #DecoderControl object. This function - * is only valid in the decoder thread. The object must be locked - * prior to calling this function. - */ - void Wait() noexcept { - cond.wait(mutex); - } - - /** - * Waits for a signal from the decoder thread. This object - * must be locked prior to calling this function. This method - * is only valid in the player thread. - * - * Caller must hold the lock. - */ - void WaitForDecoder() noexcept; - - bool IsIdle() const noexcept { - return state == DecoderState::STOP || - state == DecoderState::ERROR; - } - - gcc_pure - bool LockIsIdle() const noexcept { - const std::lock_guard protect(mutex); - return IsIdle(); - } - - bool IsStarting() const noexcept { - return state == DecoderState::START; - } - - gcc_pure - bool LockIsStarting() const noexcept { - const std::lock_guard protect(mutex); - return IsStarting(); - } - - bool HasFailed() const noexcept { - assert(command == DecoderCommand::NONE); - - return state == DecoderState::ERROR; - } - - gcc_pure - bool LockHasFailed() const noexcept { - const std::lock_guard protect(mutex); - return HasFailed(); - } - - /** - * Transition this obejct from DecoderState::START to - * DecoderState::DECODE. - * - * Caller must lock the object. - */ - void SetReady(const AudioFormat audio_format, - bool _seekable, SignedSongTime _duration) noexcept; - - /** - * Checks whether an error has occurred, and if so, rethrows - * it. - * - * Caller must lock the object. - */ - void CheckRethrowError() const { - assert(command == DecoderCommand::NONE); - assert(state != DecoderState::ERROR || error); - - if (state == DecoderState::ERROR) - std::rethrow_exception(error); - } - - /** - * Like CheckRethrowError(), but locks and unlocks the object. - */ - void LockCheckRethrowError() const { - const std::lock_guard protect(mutex); - CheckRethrowError(); - } - - /** - * Clear the error condition (if any). - * - * Caller must lock the object. - */ - void ClearError() noexcept { - if (state == DecoderState::ERROR) { - error = std::exception_ptr(); - state = DecoderState::STOP; - } - } - - /** - * Check if the specified song is currently being decoded. If the - * decoder is not running currently (or being started), then this - * function returns false in any case. - * - * Caller must lock the object. - */ - gcc_pure - bool IsCurrentSong(const DetachedSong &_song) const noexcept; - - gcc_pure - bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept { - return seekable && IsCurrentSong(_song); - } - -private: - /** - * Wait for the command to be finished by the decoder thread. - * - * To be called from the client thread. Caller must lock the - * object. - */ - void WaitCommandLocked() noexcept { - while (command != DecoderCommand::NONE) - WaitForDecoder(); - } - - /** - * Send a command to the decoder thread and synchronously wait - * for it to finish. - * - * To be called from the client thread. Caller must lock the - * object. - */ - void SynchronousCommandLocked(DecoderCommand cmd) noexcept { - command = cmd; - Signal(); - WaitCommandLocked(); - } - - /** - * Send a command to the decoder thread and synchronously wait - * for it to finish. - * - * To be called from the client thread. This method locks the - * object. - */ - void LockSynchronousCommand(DecoderCommand cmd) noexcept { - const std::lock_guard protect(mutex); - ClearError(); - SynchronousCommandLocked(cmd); - } - - void LockAsynchronousCommand(DecoderCommand cmd) noexcept { - const std::lock_guard protect(mutex); - command = cmd; - Signal(); - } - -public: - /** - * Marks the current command as "finished" and notifies the - * client (= player thread). - * - * To be called from the decoder thread. Caller must lock the - * mutex. - */ - void CommandFinishedLocked() noexcept { - assert(command != DecoderCommand::NONE); - - command = DecoderCommand::NONE; - client_cond.signal(); - } - - /** - * Start the decoder. - * - * Caller must lock the object. - * - * @param song the song to be decoded; the given instance will be - * owned and freed by the decoder - * @param start_time see #DecoderControl - * @param end_time see #DecoderControl - * @param pipe the pipe which receives the decoded chunks (owned by - * the caller) - */ - void Start(std::unique_ptr song, - SongTime start_time, SongTime end_time, - MusicBuffer &buffer, - std::shared_ptr pipe) noexcept; - - /** - * Caller must lock the object. - */ - void Stop() noexcept; - - /** - * Throws #std::runtime_error on error. - * - * Caller must lock the object. - */ - void Seek(SongTime t); - - void Quit() noexcept; - - const char *GetMixRampStart() const noexcept { - return mix_ramp.GetStart(); - } - - const char *GetMixRampEnd() const noexcept { - return mix_ramp.GetEnd(); - } - - const char *GetMixRampPreviousEnd() const noexcept { - return previous_mix_ramp.GetEnd(); - } - - void SetMixRamp(MixRampInfo &&new_value) noexcept { - mix_ramp = std::move(new_value); - } - - /** - * Move mixramp_end to mixramp_prev_end and clear - * mixramp_start/mixramp_end. - */ - void CycleMixRamp() noexcept; - -private: - void RunThread() noexcept; - - /* virtual methods from class InputStreamHandler */ - void OnInputStreamReady() noexcept override { - cond.signal(); - } - - void OnInputStreamAvailable() noexcept override { - cond.signal(); - } -}; - -#endif diff --git a/src/decoder/DecoderError.cxx b/src/decoder/DecoderError.cxx deleted file mode 100644 index 6fbf64b5d..000000000 --- a/src/decoder/DecoderError.cxx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2003-2017 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 "DecoderError.hxx" -#include "util/Domain.hxx" - -const Domain decoder_domain("decoder"); diff --git a/src/decoder/DecoderError.hxx b/src/decoder/DecoderError.hxx deleted file mode 100644 index 5e976e7b9..000000000 --- a/src/decoder/DecoderError.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2003-2017 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_DECODER_ERROR_HXX -#define MPD_DECODER_ERROR_HXX - -extern const class Domain decoder_domain; - -#endif diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx deleted file mode 100644 index cf1a7d679..000000000 --- a/src/decoder/DecoderThread.cxx +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright 2003-2017 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 "DecoderControl.hxx" -#include "Bridge.hxx" -#include "DecoderError.hxx" -#include "DecoderPlugin.hxx" -#include "song/DetachedSong.hxx" -#include "MusicPipe.hxx" -#include "fs/Traits.hxx" -#include "fs/AllocatedPath.hxx" -#include "DecoderAPI.hxx" -#include "input/InputStream.hxx" -#include "input/LocalOpen.hxx" -#include "DecoderList.hxx" -#include "system/Error.hxx" -#include "util/MimeType.hxx" -#include "util/UriUtil.hxx" -#include "util/RuntimeError.hxx" -#include "util/Domain.hxx" -#include "util/ScopeExit.hxx" -#include "thread/Name.hxx" -#include "tag/ApeReplayGain.hxx" -#include "Log.hxx" - -#include -#include -#include - -static constexpr Domain decoder_thread_domain("decoder_thread"); - -/** - * Opens the input stream with InputStream::Open(), and waits until - * the stream gets ready. - * - * Unlock the decoder before calling this function. - */ -static InputStreamPtr -decoder_input_stream_open(DecoderControl &dc, const char *uri) -{ - auto is = InputStream::Open(uri, dc.mutex); - is->SetHandler(&dc); - - /* wait for the input stream to become ready; its metadata - will be available then */ - - const std::lock_guard protect(dc.mutex); - - is->Update(); - while (!is->IsReady()) { - if (dc.command == DecoderCommand::STOP) - throw StopDecoder(); - - dc.Wait(); - - is->Update(); - } - - is->Check(); - - return is; -} - -static InputStreamPtr -decoder_input_stream_open(DecoderControl &dc, Path path) -{ - auto is = OpenLocalInputStream(path, dc.mutex); - - assert(is->IsReady()); - - return is; -} - -/** - * Decode a stream with the given decoder plugin. - * - * Caller holds DecoderControl::mutex. - */ -static bool -decoder_stream_decode(const DecoderPlugin &plugin, - DecoderBridge &bridge, - InputStream &input_stream) -{ - assert(plugin.stream_decode != nullptr); - assert(bridge.stream_tag == nullptr); - assert(bridge.decoder_tag == nullptr); - assert(input_stream.IsReady()); - assert(bridge.dc.state == DecoderState::START); - - FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); - - if (bridge.dc.command == DecoderCommand::STOP) - throw StopDecoder(); - - /* rewind the stream, so each plugin gets a fresh start */ - try { - input_stream.Rewind(); - } catch (...) { - } - - { - const ScopeUnlock unlock(bridge.dc.mutex); - - FormatThreadName("decoder:%s", plugin.name); - - plugin.StreamDecode(bridge, input_stream); - - SetThreadName("decoder"); - } - - assert(bridge.dc.state == DecoderState::START || - bridge.dc.state == DecoderState::DECODE); - - return bridge.dc.state != DecoderState::START; -} - -/** - * Decode a file with the given decoder plugin. - * - * Caller holds DecoderControl::mutex. - */ -static bool -decoder_file_decode(const DecoderPlugin &plugin, - DecoderBridge &bridge, Path path) -{ - assert(plugin.file_decode != nullptr); - assert(bridge.stream_tag == nullptr); - assert(bridge.decoder_tag == nullptr); - assert(!path.IsNull()); - assert(path.IsAbsolute()); - assert(bridge.dc.state == DecoderState::START); - - FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); - - if (bridge.dc.command == DecoderCommand::STOP) - throw StopDecoder(); - - { - const ScopeUnlock unlock(bridge.dc.mutex); - - FormatThreadName("decoder:%s", plugin.name); - - plugin.FileDecode(bridge, path); - - SetThreadName("decoder"); - } - - assert(bridge.dc.state == DecoderState::START || - bridge.dc.state == DecoderState::DECODE); - - return bridge.dc.state != DecoderState::START; -} - -gcc_pure -static bool -decoder_check_plugin_mime(const DecoderPlugin &plugin, - const InputStream &is) noexcept -{ - assert(plugin.stream_decode != nullptr); - - const char *mime_type = is.GetMimeType(); - return mime_type != nullptr && - plugin.SupportsMimeType(GetMimeTypeBase(mime_type).c_str()); -} - -gcc_pure -static bool -decoder_check_plugin_suffix(const DecoderPlugin &plugin, - const char *suffix) noexcept -{ - assert(plugin.stream_decode != nullptr); - - return suffix != nullptr && plugin.SupportsSuffix(suffix); -} - -gcc_pure -static bool -decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, - const char *suffix) noexcept -{ - return plugin.stream_decode != nullptr && - (decoder_check_plugin_mime(plugin, is) || - decoder_check_plugin_suffix(plugin, suffix)); -} - -static bool -decoder_run_stream_plugin(DecoderBridge &bridge, InputStream &is, - const char *suffix, - const DecoderPlugin &plugin, - bool &tried_r) -{ - if (!decoder_check_plugin(plugin, is, suffix)) - return false; - - bridge.error = std::exception_ptr(); - - tried_r = true; - return decoder_stream_decode(plugin, bridge, is); -} - -static bool -decoder_run_stream_locked(DecoderBridge &bridge, InputStream &is, - const char *uri, bool &tried_r) -{ - UriSuffixBuffer suffix_buffer; - const char *const suffix = uri_get_suffix(uri, suffix_buffer); - - using namespace std::placeholders; - const auto f = std::bind(decoder_run_stream_plugin, - std::ref(bridge), std::ref(is), suffix, - _1, std::ref(tried_r)); - return decoder_plugins_try(f); -} - -/** - * Try decoding a stream, using the fallback plugin. - */ -static bool -decoder_run_stream_fallback(DecoderBridge &bridge, InputStream &is) -{ - const struct DecoderPlugin *plugin; - -#ifdef ENABLE_FFMPEG - plugin = decoder_plugin_from_name("ffmpeg"); -#else - plugin = decoder_plugin_from_name("mad"); -#endif - return plugin != nullptr && plugin->stream_decode != nullptr && - decoder_stream_decode(*plugin, bridge, is); -} - -/** - * Attempt to load replay gain data, and pass it to - * DecoderClient::SubmitReplayGain(). - */ -static void -LoadReplayGain(DecoderClient &client, InputStream &is) -{ - ReplayGainInfo info; - if (replay_gain_ape_read(is, info)) - client.SubmitReplayGain(&info); -} - -/** - * Call LoadReplayGain() unless ReplayGain is disabled. This saves - * the I/O overhead when the user is not interested in the feature. - */ -static void -MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is) -{ - { - const std::lock_guard protect(bridge.dc.mutex); - if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF) - /* ReplayGain is disabled */ - return; - } - - LoadReplayGain(bridge, is); -} - -/** - * Try decoding a stream. - * - * DecoderControl::mutex is not locked by caller. - */ -static bool -decoder_run_stream(DecoderBridge &bridge, const char *uri) -{ - DecoderControl &dc = bridge.dc; - - auto input_stream = decoder_input_stream_open(dc, uri); - assert(input_stream); - - MaybeLoadReplayGain(bridge, *input_stream); - - const std::lock_guard protect(dc.mutex); - - bool tried = false; - return dc.command == DecoderCommand::STOP || - decoder_run_stream_locked(bridge, *input_stream, uri, - tried) || - /* fallback to mp3: this is needed for bastard streams - that don't have a suffix or set the mimeType */ - (!tried && - decoder_run_stream_fallback(bridge, *input_stream)); -} - -/** - * Decode a file with the given decoder plugin. - * - * DecoderControl::mutex is not locked by caller. - */ -static bool -TryDecoderFile(DecoderBridge &bridge, Path path_fs, const char *suffix, - InputStream &input_stream, - const DecoderPlugin &plugin) -{ - if (!plugin.SupportsSuffix(suffix)) - return false; - - bridge.error = std::exception_ptr(); - - DecoderControl &dc = bridge.dc; - - if (plugin.file_decode != nullptr) { - const std::lock_guard protect(dc.mutex); - return decoder_file_decode(plugin, bridge, path_fs); - } else if (plugin.stream_decode != nullptr) { - const std::lock_guard protect(dc.mutex); - return decoder_stream_decode(plugin, bridge, input_stream); - } else - return false; -} - -/** - * Decode a container file with the given decoder plugin. - * - * DecoderControl::mutex is not locked by caller. - */ -static bool -TryContainerDecoder(DecoderBridge &bridge, Path path_fs, const char *suffix, - const DecoderPlugin &plugin) -{ - if (plugin.container_scan == nullptr || - plugin.file_decode == nullptr || - !plugin.SupportsSuffix(suffix)) - return false; - - bridge.error = nullptr; - - DecoderControl &dc = bridge.dc; - const std::lock_guard protect(dc.mutex); - return decoder_file_decode(plugin, bridge, path_fs); -} - -/** - * Decode a container file. - * - * DecoderControl::mutex is not locked by caller. - */ -static bool -TryContainerDecoder(DecoderBridge &bridge, Path path_fs, const char *suffix) -{ - return decoder_plugins_try([&bridge, path_fs, - suffix](const DecoderPlugin &plugin){ - return TryContainerDecoder(bridge, - path_fs, - suffix, - plugin); - }); -} - -/** - * Try decoding a file. - * - * DecoderControl::mutex is not locked by caller. - */ -static bool -decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs) -{ - const char *suffix = uri_get_suffix(uri_utf8); - if (suffix == nullptr) - return false; - - InputStreamPtr input_stream; - - try { - input_stream = decoder_input_stream_open(bridge.dc, path_fs); - } catch (const std::system_error &e) { - if (IsPathNotFound(e) && - /* ENOTDIR means this may be a path inside a - "container" file */ - TryContainerDecoder(bridge, path_fs, suffix)) - return true; - - throw; - } - - assert(input_stream); - - MaybeLoadReplayGain(bridge, *input_stream); - - auto &is = *input_stream; - return decoder_plugins_try([&bridge, path_fs, suffix, - &is](const DecoderPlugin &plugin){ - return TryDecoderFile(bridge, - path_fs, - suffix, - is, - plugin); - }); -} - -/** - * Decode a song. - * - * DecoderControl::mutex is not locked. - */ -static bool -DecoderUnlockedRunUri(DecoderBridge &bridge, - const char *real_uri, Path path_fs) -try { - return !path_fs.IsNull() - ? decoder_run_file(bridge, real_uri, path_fs) - : decoder_run_stream(bridge, real_uri); -} catch (StopDecoder) { - return true; -} catch (...) { - const char *error_uri = real_uri; - const std::string allocated = uri_remove_auth(error_uri); - if (!allocated.empty()) - error_uri = allocated.c_str(); - - std::throw_with_nested(FormatRuntimeError("Failed to decode %s", - error_uri)); -} - -/** - * Decode a song addressed by a #DetachedSong. - * - * Caller holds DecoderControl::mutex. - */ -static void -decoder_run_song(DecoderControl &dc, - const DetachedSong &song, const char *uri, Path path_fs) -{ - DecoderBridge bridge(dc, dc.start_time.IsPositive(), - /* pass the song tag only if it's - authoritative, i.e. if it's a local - file - tags on "stream" songs are just - remembered from the last time we - played it*/ - song.IsFile() ? std::make_unique(song.GetTag()) : nullptr); - - dc.state = DecoderState::START; - dc.CommandFinishedLocked(); - - bool success; - { - const ScopeUnlock unlock(dc.mutex); - - AtScopeExit(&bridge) { - /* flush the last chunk */ - if (bridge.current_chunk != nullptr) - bridge.FlushChunk(); - }; - - success = DecoderUnlockedRunUri(bridge, uri, path_fs); - - } - - if (bridge.error) { - /* copy the Error from struct Decoder to - DecoderControl */ - std::rethrow_exception(bridge.error); - } else if (success) - dc.state = DecoderState::STOP; - else { - const char *error_uri = song.GetURI(); - const std::string allocated = uri_remove_auth(error_uri); - if (!allocated.empty()) - error_uri = allocated.c_str(); - - throw FormatRuntimeError("Failed to decode %s", error_uri); - } - - dc.client_cond.signal(); -} - -/** - * - * Caller holds DecoderControl::mutex. - */ -static void -decoder_run(DecoderControl &dc) -try { - dc.ClearError(); - - assert(dc.song != nullptr); - const DetachedSong &song = *dc.song; - - const char *const uri_utf8 = song.GetRealURI(); - - Path path_fs = nullptr; - AllocatedPath path_buffer = nullptr; - if (PathTraitsUTF8::IsAbsolute(uri_utf8)) { - path_buffer = AllocatedPath::FromUTF8Throw(uri_utf8); - path_fs = path_buffer; - } - - decoder_run_song(dc, song, uri_utf8, path_fs); -} catch (...) { - dc.state = DecoderState::ERROR; - dc.command = DecoderCommand::NONE; - dc.error = std::current_exception(); - dc.client_cond.signal(); -} - -void -DecoderControl::RunThread() noexcept -{ - SetThreadName("decoder"); - - const std::lock_guard protect(mutex); - - do { - assert(state == DecoderState::STOP || - state == DecoderState::ERROR); - - switch (command) { - case DecoderCommand::START: - CycleMixRamp(); - replay_gain_prev_db = replay_gain_db; - replay_gain_db = 0; - - decoder_run(*this); - - if (state == DecoderState::ERROR) { - try { - std::rethrow_exception(error); - } catch (...) { - LogError(std::current_exception()); - } - } - - break; - - case DecoderCommand::SEEK: - /* this seek was too late, and the decoder had - already finished; start a new decoder */ - - /* we need to clear the pipe here; usually the - PlayerThread is responsible, but it is not - aware that the decoder has finished */ - pipe->Clear(); - - decoder_run(*this); - break; - - case DecoderCommand::STOP: - CommandFinishedLocked(); - break; - - case DecoderCommand::NONE: - Wait(); - break; - } - } while (command != DecoderCommand::NONE || !quit); -} diff --git a/src/decoder/Domain.cxx b/src/decoder/Domain.cxx new file mode 100644 index 000000000..70adbb241 --- /dev/null +++ b/src/decoder/Domain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright 2003-2017 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 "Domain.hxx" +#include "util/Domain.hxx" + +const Domain decoder_domain("decoder"); diff --git a/src/decoder/Domain.hxx b/src/decoder/Domain.hxx new file mode 100644 index 000000000..edac6e286 --- /dev/null +++ b/src/decoder/Domain.hxx @@ -0,0 +1,25 @@ +/* + * Copyright 2003-2017 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_DECODER_DOMAIN_HXX +#define MPD_DECODER_DOMAIN_HXX + +extern const class Domain decoder_domain; + +#endif diff --git a/src/decoder/Thread.cxx b/src/decoder/Thread.cxx new file mode 100644 index 000000000..5a7aca103 --- /dev/null +++ b/src/decoder/Thread.cxx @@ -0,0 +1,565 @@ +/* + * Copyright 2003-2017 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 "Bridge.hxx" +#include "DecoderPlugin.hxx" +#include "song/DetachedSong.hxx" +#include "MusicPipe.hxx" +#include "fs/Traits.hxx" +#include "fs/AllocatedPath.hxx" +#include "DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "input/LocalOpen.hxx" +#include "DecoderList.hxx" +#include "system/Error.hxx" +#include "util/MimeType.hxx" +#include "util/UriUtil.hxx" +#include "util/RuntimeError.hxx" +#include "util/Domain.hxx" +#include "util/ScopeExit.hxx" +#include "thread/Name.hxx" +#include "tag/ApeReplayGain.hxx" +#include "Log.hxx" + +#include +#include +#include + +static constexpr Domain decoder_thread_domain("decoder_thread"); + +/** + * Opens the input stream with InputStream::Open(), and waits until + * the stream gets ready. + * + * Unlock the decoder before calling this function. + */ +static InputStreamPtr +decoder_input_stream_open(DecoderControl &dc, const char *uri) +{ + auto is = InputStream::Open(uri, dc.mutex); + is->SetHandler(&dc); + + /* wait for the input stream to become ready; its metadata + will be available then */ + + const std::lock_guard protect(dc.mutex); + + is->Update(); + while (!is->IsReady()) { + if (dc.command == DecoderCommand::STOP) + throw StopDecoder(); + + dc.Wait(); + + is->Update(); + } + + is->Check(); + + return is; +} + +static InputStreamPtr +decoder_input_stream_open(DecoderControl &dc, Path path) +{ + auto is = OpenLocalInputStream(path, dc.mutex); + + assert(is->IsReady()); + + return is; +} + +/** + * Decode a stream with the given decoder plugin. + * + * Caller holds DecoderControl::mutex. + */ +static bool +decoder_stream_decode(const DecoderPlugin &plugin, + DecoderBridge &bridge, + InputStream &input_stream) +{ + assert(plugin.stream_decode != nullptr); + assert(bridge.stream_tag == nullptr); + assert(bridge.decoder_tag == nullptr); + assert(input_stream.IsReady()); + assert(bridge.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (bridge.dc.command == DecoderCommand::STOP) + throw StopDecoder(); + + /* rewind the stream, so each plugin gets a fresh start */ + try { + input_stream.Rewind(); + } catch (...) { + } + + { + const ScopeUnlock unlock(bridge.dc.mutex); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.StreamDecode(bridge, input_stream); + + SetThreadName("decoder"); + } + + assert(bridge.dc.state == DecoderState::START || + bridge.dc.state == DecoderState::DECODE); + + return bridge.dc.state != DecoderState::START; +} + +/** + * Decode a file with the given decoder plugin. + * + * Caller holds DecoderControl::mutex. + */ +static bool +decoder_file_decode(const DecoderPlugin &plugin, + DecoderBridge &bridge, Path path) +{ + assert(plugin.file_decode != nullptr); + assert(bridge.stream_tag == nullptr); + assert(bridge.decoder_tag == nullptr); + assert(!path.IsNull()); + assert(path.IsAbsolute()); + assert(bridge.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (bridge.dc.command == DecoderCommand::STOP) + throw StopDecoder(); + + { + const ScopeUnlock unlock(bridge.dc.mutex); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.FileDecode(bridge, path); + + SetThreadName("decoder"); + } + + assert(bridge.dc.state == DecoderState::START || + bridge.dc.state == DecoderState::DECODE); + + return bridge.dc.state != DecoderState::START; +} + +gcc_pure +static bool +decoder_check_plugin_mime(const DecoderPlugin &plugin, + const InputStream &is) noexcept +{ + assert(plugin.stream_decode != nullptr); + + const char *mime_type = is.GetMimeType(); + return mime_type != nullptr && + plugin.SupportsMimeType(GetMimeTypeBase(mime_type).c_str()); +} + +gcc_pure +static bool +decoder_check_plugin_suffix(const DecoderPlugin &plugin, + const char *suffix) noexcept +{ + assert(plugin.stream_decode != nullptr); + + return suffix != nullptr && plugin.SupportsSuffix(suffix); +} + +gcc_pure +static bool +decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, + const char *suffix) noexcept +{ + return plugin.stream_decode != nullptr && + (decoder_check_plugin_mime(plugin, is) || + decoder_check_plugin_suffix(plugin, suffix)); +} + +static bool +decoder_run_stream_plugin(DecoderBridge &bridge, InputStream &is, + const char *suffix, + const DecoderPlugin &plugin, + bool &tried_r) +{ + if (!decoder_check_plugin(plugin, is, suffix)) + return false; + + bridge.error = std::exception_ptr(); + + tried_r = true; + return decoder_stream_decode(plugin, bridge, is); +} + +static bool +decoder_run_stream_locked(DecoderBridge &bridge, InputStream &is, + const char *uri, bool &tried_r) +{ + UriSuffixBuffer suffix_buffer; + const char *const suffix = uri_get_suffix(uri, suffix_buffer); + + using namespace std::placeholders; + const auto f = std::bind(decoder_run_stream_plugin, + std::ref(bridge), std::ref(is), suffix, + _1, std::ref(tried_r)); + return decoder_plugins_try(f); +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(DecoderBridge &bridge, InputStream &is) +{ + const struct DecoderPlugin *plugin; + +#ifdef ENABLE_FFMPEG + plugin = decoder_plugin_from_name("ffmpeg"); +#else + plugin = decoder_plugin_from_name("mad"); +#endif + return plugin != nullptr && plugin->stream_decode != nullptr && + decoder_stream_decode(*plugin, bridge, is); +} + +/** + * Attempt to load replay gain data, and pass it to + * DecoderClient::SubmitReplayGain(). + */ +static void +LoadReplayGain(DecoderClient &client, InputStream &is) +{ + ReplayGainInfo info; + if (replay_gain_ape_read(is, info)) + client.SubmitReplayGain(&info); +} + +/** + * Call LoadReplayGain() unless ReplayGain is disabled. This saves + * the I/O overhead when the user is not interested in the feature. + */ +static void +MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is) +{ + { + const std::lock_guard protect(bridge.dc.mutex); + if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF) + /* ReplayGain is disabled */ + return; + } + + LoadReplayGain(bridge, is); +} + +/** + * Try decoding a stream. + * + * DecoderControl::mutex is not locked by caller. + */ +static bool +decoder_run_stream(DecoderBridge &bridge, const char *uri) +{ + DecoderControl &dc = bridge.dc; + + auto input_stream = decoder_input_stream_open(dc, uri); + assert(input_stream); + + MaybeLoadReplayGain(bridge, *input_stream); + + const std::lock_guard protect(dc.mutex); + + bool tried = false; + return dc.command == DecoderCommand::STOP || + decoder_run_stream_locked(bridge, *input_stream, uri, + tried) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + (!tried && + decoder_run_stream_fallback(bridge, *input_stream)); +} + +/** + * Decode a file with the given decoder plugin. + * + * DecoderControl::mutex is not locked by caller. + */ +static bool +TryDecoderFile(DecoderBridge &bridge, Path path_fs, const char *suffix, + InputStream &input_stream, + const DecoderPlugin &plugin) +{ + if (!plugin.SupportsSuffix(suffix)) + return false; + + bridge.error = std::exception_ptr(); + + DecoderControl &dc = bridge.dc; + + if (plugin.file_decode != nullptr) { + const std::lock_guard protect(dc.mutex); + return decoder_file_decode(plugin, bridge, path_fs); + } else if (plugin.stream_decode != nullptr) { + const std::lock_guard protect(dc.mutex); + return decoder_stream_decode(plugin, bridge, input_stream); + } else + return false; +} + +/** + * Decode a container file with the given decoder plugin. + * + * DecoderControl::mutex is not locked by caller. + */ +static bool +TryContainerDecoder(DecoderBridge &bridge, Path path_fs, const char *suffix, + const DecoderPlugin &plugin) +{ + if (plugin.container_scan == nullptr || + plugin.file_decode == nullptr || + !plugin.SupportsSuffix(suffix)) + return false; + + bridge.error = nullptr; + + DecoderControl &dc = bridge.dc; + const std::lock_guard protect(dc.mutex); + return decoder_file_decode(plugin, bridge, path_fs); +} + +/** + * Decode a container file. + * + * DecoderControl::mutex is not locked by caller. + */ +static bool +TryContainerDecoder(DecoderBridge &bridge, Path path_fs, const char *suffix) +{ + return decoder_plugins_try([&bridge, path_fs, + suffix](const DecoderPlugin &plugin){ + return TryContainerDecoder(bridge, + path_fs, + suffix, + plugin); + }); +} + +/** + * Try decoding a file. + * + * DecoderControl::mutex is not locked by caller. + */ +static bool +decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs) +{ + const char *suffix = uri_get_suffix(uri_utf8); + if (suffix == nullptr) + return false; + + InputStreamPtr input_stream; + + try { + input_stream = decoder_input_stream_open(bridge.dc, path_fs); + } catch (const std::system_error &e) { + if (IsPathNotFound(e) && + /* ENOTDIR means this may be a path inside a + "container" file */ + TryContainerDecoder(bridge, path_fs, suffix)) + return true; + + throw; + } + + assert(input_stream); + + MaybeLoadReplayGain(bridge, *input_stream); + + auto &is = *input_stream; + return decoder_plugins_try([&bridge, path_fs, suffix, + &is](const DecoderPlugin &plugin){ + return TryDecoderFile(bridge, + path_fs, + suffix, + is, + plugin); + }); +} + +/** + * Decode a song. + * + * DecoderControl::mutex is not locked. + */ +static bool +DecoderUnlockedRunUri(DecoderBridge &bridge, + const char *real_uri, Path path_fs) +try { + return !path_fs.IsNull() + ? decoder_run_file(bridge, real_uri, path_fs) + : decoder_run_stream(bridge, real_uri); +} catch (StopDecoder) { + return true; +} catch (...) { + const char *error_uri = real_uri; + const std::string allocated = uri_remove_auth(error_uri); + if (!allocated.empty()) + error_uri = allocated.c_str(); + + std::throw_with_nested(FormatRuntimeError("Failed to decode %s", + error_uri)); +} + +/** + * Decode a song addressed by a #DetachedSong. + * + * Caller holds DecoderControl::mutex. + */ +static void +decoder_run_song(DecoderControl &dc, + const DetachedSong &song, const char *uri, Path path_fs) +{ + DecoderBridge bridge(dc, dc.start_time.IsPositive(), + /* pass the song tag only if it's + authoritative, i.e. if it's a local + file - tags on "stream" songs are just + remembered from the last time we + played it*/ + song.IsFile() ? std::make_unique(song.GetTag()) : nullptr); + + dc.state = DecoderState::START; + dc.CommandFinishedLocked(); + + bool success; + { + const ScopeUnlock unlock(dc.mutex); + + AtScopeExit(&bridge) { + /* flush the last chunk */ + if (bridge.current_chunk != nullptr) + bridge.FlushChunk(); + }; + + success = DecoderUnlockedRunUri(bridge, uri, path_fs); + + } + + if (bridge.error) { + /* copy the Error from struct Decoder to + DecoderControl */ + std::rethrow_exception(bridge.error); + } else if (success) + dc.state = DecoderState::STOP; + else { + const char *error_uri = song.GetURI(); + const std::string allocated = uri_remove_auth(error_uri); + if (!allocated.empty()) + error_uri = allocated.c_str(); + + throw FormatRuntimeError("Failed to decode %s", error_uri); + } + + dc.client_cond.signal(); +} + +/** + * + * Caller holds DecoderControl::mutex. + */ +static void +decoder_run(DecoderControl &dc) +try { + dc.ClearError(); + + assert(dc.song != nullptr); + const DetachedSong &song = *dc.song; + + const char *const uri_utf8 = song.GetRealURI(); + + Path path_fs = nullptr; + AllocatedPath path_buffer = nullptr; + if (PathTraitsUTF8::IsAbsolute(uri_utf8)) { + path_buffer = AllocatedPath::FromUTF8Throw(uri_utf8); + path_fs = path_buffer; + } + + decoder_run_song(dc, song, uri_utf8, path_fs); +} catch (...) { + dc.state = DecoderState::ERROR; + dc.command = DecoderCommand::NONE; + dc.error = std::current_exception(); + dc.client_cond.signal(); +} + +void +DecoderControl::RunThread() noexcept +{ + SetThreadName("decoder"); + + const std::lock_guard protect(mutex); + + do { + assert(state == DecoderState::STOP || + state == DecoderState::ERROR); + + switch (command) { + case DecoderCommand::START: + CycleMixRamp(); + replay_gain_prev_db = replay_gain_db; + replay_gain_db = 0; + + decoder_run(*this); + + if (state == DecoderState::ERROR) { + try { + std::rethrow_exception(error); + } catch (...) { + LogError(std::current_exception()); + } + } + + break; + + case DecoderCommand::SEEK: + /* this seek was too late, and the decoder had + already finished; start a new decoder */ + + /* we need to clear the pipe here; usually the + PlayerThread is responsible, but it is not + aware that the decoder has finished */ + pipe->Clear(); + + decoder_run(*this); + break; + + case DecoderCommand::STOP: + CommandFinishedLocked(); + break; + + case DecoderCommand::NONE: + Wait(); + break; + } + } while (command != DecoderCommand::NONE || !quit); +} diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx index 6a7ccd024..11c360fdf 100644 --- a/src/player/Thread.cxx +++ b/src/player/Thread.cxx @@ -21,7 +21,7 @@ #include "Thread.hxx" #include "Outputs.hxx" #include "Listener.hxx" -#include "decoder/DecoderControl.hxx" +#include "decoder/Control.hxx" #include "MusicPipe.hxx" #include "MusicBuffer.hxx" #include "MusicChunk.hxx" -- cgit v1.2.3