diff --git a/Makefile.am b/Makefile.am index 3b1466839..1bff2e27b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1340,6 +1340,7 @@ OUTPUT_API_SRC = \ src/output/Registry.cxx src/output/Registry.hxx \ src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \ src/output/SharedPipeConsumer.cxx src/output/SharedPipeConsumer.hxx \ + src/output/Source.cxx src/output/Source.hxx \ src/output/OutputThread.cxx \ src/output/Domain.cxx src/output/Domain.hxx \ src/output/OutputControl.cxx \ diff --git a/src/output/Init.cxx b/src/output/Init.cxx index 65234e9bd..38908c37b 100644 --- a/src/output/Init.cxx +++ b/src/output/Init.cxx @@ -222,13 +222,9 @@ audio_output_setup(EventLoop &event_loop, NewReplayGainFilter(replay_gain_config); assert(ao.prepared_replay_gain_filter != nullptr); - ao.replay_gain_serial = 0; - ao.prepared_other_replay_gain_filter = NewReplayGainFilter(replay_gain_config); assert(ao.prepared_other_replay_gain_filter != nullptr); - - ao.other_replay_gain_serial = 0; } else { ao.prepared_replay_gain_filter = nullptr; ao.prepared_other_replay_gain_filter = nullptr; diff --git a/src/output/Internal.cxx b/src/output/Internal.cxx index a6045f487..da64eb5f8 100644 --- a/src/output/Internal.cxx +++ b/src/output/Internal.cxx @@ -26,5 +26,5 @@ AudioOutput::IsChunkConsumed(const MusicChunk &chunk) const if (!open) return true; - return pipe.IsConsumed(chunk); + return source.IsChunkConsumed(chunk); } diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx index f84390fef..b15138fcb 100644 --- a/src/output/Internal.hxx +++ b/src/output/Internal.hxx @@ -20,11 +20,9 @@ #ifndef MPD_OUTPUT_INTERNAL_HXX #define MPD_OUTPUT_INTERNAL_HXX +#include "Source.hxx" #include "SharedPipeConsumer.hxx" #include "AudioFormat.hxx" -#include "pcm/PcmBuffer.hxx" -#include "pcm/PcmDither.hxx" -#include "ReplayGainMode.hxx" #include "filter/Observer.hxx" #include "thread/Mutex.hxx" #include "thread/Cond.hxx" @@ -149,8 +147,6 @@ struct AudioOutput { */ bool woken_for_play = false; - ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; - /** * If not nullptr, the device has failed, and this timer is used * to estimate how long it should stay disabled (unless @@ -163,14 +159,6 @@ struct AudioOutput { */ AudioFormat config_audio_format; - /** - * The audio_format in which audio data is received from the - * player thread (which in turn receives it from the decoder). - * - * Only accessible from within the OutputThread. - */ - AudioFormat in_audio_format; - /** * The #AudioFormat which is emitted by the #Filter, with * #config_audio_format already applied. This is used to @@ -187,22 +175,11 @@ struct AudioOutput { */ AudioFormat out_audio_format; - /** - * The buffer used to allocate the cross-fading result. - */ - PcmBuffer cross_fade_buffer; - - /** - * The dithering state for cross-fading two streams. - */ - PcmDither cross_fade_dither; - /** * The filter object of this audio output. This is an * instance of chain_filter_plugin. */ PreparedFilter *prepared_filter = nullptr; - Filter *filter_instance = nullptr; /** * The #VolumeFilter instance of this audio output. It is @@ -215,13 +192,6 @@ struct AudioOutput { * output. */ PreparedFilter *prepared_replay_gain_filter = nullptr; - Filter *replay_gain_filter_instance = nullptr; - - /** - * The serial number of the last replay gain info. 0 means no - * replay gain info was available. - */ - unsigned replay_gain_serial; /** * The replay_gain_filter_plugin instance of this audio @@ -229,13 +199,6 @@ struct AudioOutput { * cross-fading. */ PreparedFilter *prepared_other_replay_gain_filter = nullptr; - Filter *other_replay_gain_filter_instance = nullptr; - - /** - * The serial number of the last replay gain info by the - * "other" chunk during cross-fading. - */ - unsigned other_replay_gain_serial; /** * The convert_filter_plugin instance of this audio output. @@ -289,9 +252,9 @@ struct AudioOutput { AudioOutputClient *client; /** - * A reference to the #MusicPipe and the current position. + * Source of audio data. */ - SharedPipeConsumer pipe; + AudioOutputSource source; /** * Throws #std::runtime_error on error. @@ -394,7 +357,7 @@ public: void LockRelease(); void SetReplayGainMode(ReplayGainMode _mode) { - replay_gain_mode = _mode; + source.SetReplayGainMode(_mode); } /** @@ -442,7 +405,7 @@ public: } void ClearTailChunk(const MusicChunk &chunk) { - pipe.ClearTail(chunk); + source.ClearTailChunk(chunk); } private: diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx index f92d058ba..31dfcaead 100644 --- a/src/output/OutputControl.cxx +++ b/src/output/OutputControl.cxx @@ -113,7 +113,7 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) fail_timer.Reset(); if (open && audio_format == request.audio_format) { - assert(&pipe.GetPipe() == &mp || (always_on && pause)); + assert(request.pipe == &mp || (always_on && pause)); if (!pause) /* already open, already the right parameters diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx index 884edf3b2..f9d78bac7 100644 --- a/src/output/OutputThread.cxx +++ b/src/output/OutputThread.cxx @@ -91,27 +91,18 @@ AudioOutput::Disable() inline AudioFormat AudioOutput::OpenFilter(AudioFormat &format) -try { +{ assert(format.IsValid()); - /* the replay_gain filter cannot fail here */ - if (prepared_replay_gain_filter != nullptr) - replay_gain_filter_instance = - prepared_replay_gain_filter->Open(format); - - if (prepared_other_replay_gain_filter != nullptr) - other_replay_gain_filter_instance = - prepared_other_replay_gain_filter->Open(format); - - filter_instance = prepared_filter->Open(format); + const auto result = source.Open(format, *request.pipe, + prepared_replay_gain_filter, + prepared_other_replay_gain_filter, + prepared_filter); if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin)) software_mixer_set_filter(*mixer, volume_filter.Get()); - return filter_instance->GetOutAudioFormat(); -} catch (...) { - CloseFilter(); - throw; + return result; } void @@ -120,14 +111,7 @@ AudioOutput::CloseFilter() if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin)) software_mixer_set_filter(*mixer, nullptr); - delete replay_gain_filter_instance; - replay_gain_filter_instance = nullptr; - - delete other_replay_gain_filter_instance; - other_replay_gain_filter_instance = nullptr; - - delete filter_instance; - filter_instance = nullptr; + source.Close(); } inline void @@ -144,41 +128,29 @@ AudioOutput::Open() return; } - if (!open || request.pipe != &pipe.GetPipe()) - pipe.Init(*request.pipe); + AudioFormat f; - /* (re)open the filter */ - - if (filter_instance != nullptr && - request.audio_format != in_audio_format) - /* the filter must be reopened on all input format - changes */ - CloseFilter(); - - if (filter_instance == nullptr) { - /* open the filter */ - AudioFormat f; - try { - f = OpenFilter(request.audio_format) - .WithMask(config_audio_format); - } catch (const std::runtime_error &e) { - FormatError(e, "Failed to open filter for \"%s\" [%s]", - name, plugin.name); - fail_timer.Update(); - return; - } - - if (open && f != filter_audio_format) { - /* if the filter's output format changes, the - outpuit must be reopened as well */ - CloseOutput(true); - open = false; - } - - filter_audio_format = f; + try { + f = source.Open(request.audio_format, *request.pipe, + prepared_replay_gain_filter, + prepared_other_replay_gain_filter, + prepared_filter) + .WithMask(config_audio_format); + } catch (const std::runtime_error &e) { + FormatError(e, "Failed to open filter for \"%s\" [%s]", + name, plugin.name); + fail_timer.Update(); + return; } - in_audio_format = request.audio_format; + if (open && f != filter_audio_format) { + /* if the filter's output format changes, the output + must be reopened as well */ + CloseOutput(true); + open = false; + } + + filter_audio_format = f; if (!open) { if (OpenOutputAndConvert(filter_audio_format)) { @@ -233,9 +205,9 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format) plugin.name, name, audio_format_to_string(out_audio_format, &af_string)); - if (in_audio_format != out_audio_format) + if (source.GetInputAudioFormat() != out_audio_format) FormatDebug(output_domain, "converting from %s", - audio_format_to_string(in_audio_format, + audio_format_to_string(source.GetInputAudioFormat(), &af_string)); return true; @@ -246,8 +218,6 @@ AudioOutput::Close(bool drain) { assert(open); - pipe.Cancel(); - open = false; const ScopeUnlock unlock(mutex); @@ -291,94 +261,9 @@ AudioOutput::WaitForDelay() } } -static ConstBuffer -ao_chunk_data(AudioOutput &ao, const MusicChunk &chunk, - Filter *replay_gain_filter, - unsigned *replay_gain_serial_p) -{ - assert(!chunk.IsEmpty()); - assert(chunk.CheckFormat(ao.in_audio_format)); - - ConstBuffer data(chunk.data, chunk.length); - - assert(data.size % ao.in_audio_format.GetFrameSize() == 0); - - if (!data.IsEmpty() && replay_gain_filter != nullptr) { - replay_gain_filter_set_mode(*replay_gain_filter, - ao.replay_gain_mode); - - if (chunk.replay_gain_serial != *replay_gain_serial_p) { - replay_gain_filter_set_info(*replay_gain_filter, - chunk.replay_gain_serial != 0 - ? &chunk.replay_gain_info - : nullptr); - *replay_gain_serial_p = chunk.replay_gain_serial; - } - - data = replay_gain_filter->FilterPCM(data); - } - - return data; -} - -static ConstBuffer -ao_filter_chunk(AudioOutput &ao, const MusicChunk &chunk) -{ - ConstBuffer data = - ao_chunk_data(ao, chunk, ao.replay_gain_filter_instance, - &ao.replay_gain_serial); - if (data.IsEmpty()) - return data; - - /* cross-fade */ - - if (chunk.other != nullptr) { - ConstBuffer other_data = - ao_chunk_data(ao, *chunk.other, - ao.other_replay_gain_filter_instance, - &ao.other_replay_gain_serial); - if (other_data.IsEmpty()) - return data; - - /* if the "other" chunk is longer, then that trailer - is used as-is, without mixing; it is part of the - "next" song being faded in, and if there's a rest, - it means cross-fading ends here */ - - if (data.size > other_data.size) - data.size = other_data.size; - - float mix_ratio = chunk.mix_ratio; - if (mix_ratio >= 0) - /* reverse the mix ratio (because the - arguments to pcm_mix() are reversed), but - only if the mix ratio is non-negative; a - negative mix ratio is a MixRamp special - case */ - mix_ratio = 1.0 - mix_ratio; - - void *dest = ao.cross_fade_buffer.Get(other_data.size); - memcpy(dest, other_data.data, other_data.size); - if (!pcm_mix(ao.cross_fade_dither, dest, data.data, data.size, - ao.in_audio_format.format, - mix_ratio)) - throw FormatRuntimeError("Cannot cross-fade format %s", - sample_format_to_string(ao.in_audio_format.format)); - - data.data = dest; - data.size = other_data.size; - } - - /* apply filter chain */ - - return ao.filter_instance->FilterPCM(data); -} - inline bool AudioOutput::PlayChunk(const MusicChunk &chunk) { - assert(filter_instance != nullptr); - if (tags && gcc_unlikely(chunk.tag != nullptr)) { const ScopeUnlock unlock(mutex); try { @@ -392,7 +277,7 @@ AudioOutput::PlayChunk(const MusicChunk &chunk) ConstBuffer data; try { - data = data.FromVoid(ao_filter_chunk(*this, chunk)); + data = data.FromVoid(source.FilterChunk(chunk)); } catch (const std::runtime_error &e) { FormatError(e, "Failed to filter for output \"%s\" [%s]", name, plugin.name); @@ -443,7 +328,7 @@ AudioOutput::PlayChunk(const MusicChunk &chunk) inline bool AudioOutput::Play() { - const MusicChunk *chunk = pipe.Get(); + const MusicChunk *chunk = source.Get(); if (chunk == nullptr) /* no chunk available */ return false; @@ -474,8 +359,8 @@ AudioOutput::Play() if (!PlayChunk(*chunk)) break; - pipe.Consume(*chunk); - chunk = pipe.Get(); + source.Consume(*chunk); + chunk = source.Get(); } while (chunk != nullptr); const ScopeUnlock unlock(mutex); @@ -580,9 +465,6 @@ AudioOutput::Task() case Command::DRAIN: if (open) { - assert(pipe.IsInitial()); - assert(pipe.GetPipe().Peek() == nullptr); - const ScopeUnlock unlock(mutex); ao_plugin_drain(this); } @@ -591,7 +473,7 @@ AudioOutput::Task() continue; case Command::CANCEL: - pipe.Cancel(); + source.Cancel(); if (open) { const ScopeUnlock unlock(mutex); @@ -603,7 +485,7 @@ AudioOutput::Task() case Command::KILL: Disable(); - pipe.Cancel(); + source.Cancel(); CommandFinished(); return; } diff --git a/src/output/Source.cxx b/src/output/Source.cxx new file mode 100644 index 000000000..728508705 --- /dev/null +++ b/src/output/Source.cxx @@ -0,0 +1,187 @@ +/* + * Copyright 2003-2016 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 "Source.hxx" +#include "MusicChunk.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "pcm/PcmMix.hxx" +#include "util/ConstBuffer.hxx" +#include "util/RuntimeError.hxx" + +#include + +AudioFormat +AudioOutputSource::Open(AudioFormat audio_format, const MusicPipe &_pipe, + PreparedFilter *prepared_replay_gain_filter, + PreparedFilter *prepared_other_replay_gain_filter, + PreparedFilter *prepared_filter) +{ + assert(audio_format.IsValid()); + + if (!IsOpen() || &_pipe != &pipe.GetPipe()) + pipe.Init(_pipe); + + /* (re)open the filter */ + + if (filter_instance != nullptr && + audio_format != in_audio_format) + /* the filter must be reopened on all input format + changes */ + CloseFilter(); + + if (filter_instance == nullptr) + /* open the filter */ + OpenFilter(audio_format, + prepared_replay_gain_filter, + prepared_other_replay_gain_filter, + prepared_filter); + + in_audio_format = audio_format; + return filter_instance->GetOutAudioFormat(); +} + +void +AudioOutputSource::Close() +{ + assert(in_audio_format.IsValid()); + in_audio_format.Clear(); + + pipe.Cancel(); + + CloseFilter(); +} + +void +AudioOutputSource::OpenFilter(AudioFormat audio_format, + PreparedFilter *prepared_replay_gain_filter, + PreparedFilter *prepared_other_replay_gain_filter, + PreparedFilter *prepared_filter) +try { + assert(audio_format.IsValid()); + + /* the replay_gain filter cannot fail here */ + if (prepared_replay_gain_filter != nullptr) + replay_gain_filter_instance = + prepared_replay_gain_filter->Open(audio_format); + + if (prepared_other_replay_gain_filter != nullptr) + other_replay_gain_filter_instance = + prepared_other_replay_gain_filter->Open(audio_format); + + filter_instance = prepared_filter->Open(audio_format); +} catch (...) { + CloseFilter(); + throw; +} + +void +AudioOutputSource::CloseFilter() +{ + delete replay_gain_filter_instance; + replay_gain_filter_instance = nullptr; + + delete other_replay_gain_filter_instance; + other_replay_gain_filter_instance = nullptr; + + delete filter_instance; + filter_instance = nullptr; +} + +ConstBuffer +AudioOutputSource::GetChunkData(const MusicChunk &chunk, + Filter *replay_gain_filter, + unsigned *replay_gain_serial_p) +{ + assert(!chunk.IsEmpty()); + assert(chunk.CheckFormat(in_audio_format)); + + ConstBuffer data(chunk.data, chunk.length); + + assert(data.size % in_audio_format.GetFrameSize() == 0); + + if (!data.IsEmpty() && replay_gain_filter != nullptr) { + replay_gain_filter_set_mode(*replay_gain_filter, + replay_gain_mode); + + if (chunk.replay_gain_serial != *replay_gain_serial_p) { + replay_gain_filter_set_info(*replay_gain_filter, + chunk.replay_gain_serial != 0 + ? &chunk.replay_gain_info + : nullptr); + *replay_gain_serial_p = chunk.replay_gain_serial; + } + + data = replay_gain_filter->FilterPCM(data); + } + + return data; +} + +ConstBuffer +AudioOutputSource::FilterChunk(const MusicChunk &chunk) +{ + auto data = GetChunkData(chunk, replay_gain_filter_instance, + &replay_gain_serial); + if (data.IsEmpty()) + return data; + + /* cross-fade */ + + if (chunk.other != nullptr) { + auto other_data = GetChunkData(*chunk.other, + other_replay_gain_filter_instance, + &other_replay_gain_serial); + if (other_data.IsEmpty()) + return data; + + /* if the "other" chunk is longer, then that trailer + is used as-is, without mixing; it is part of the + "next" song being faded in, and if there's a rest, + it means cross-fading ends here */ + + if (data.size > other_data.size) + data.size = other_data.size; + + float mix_ratio = chunk.mix_ratio; + if (mix_ratio >= 0) + /* reverse the mix ratio (because the + arguments to pcm_mix() are reversed), but + only if the mix ratio is non-negative; a + negative mix ratio is a MixRamp special + case */ + mix_ratio = 1.0 - mix_ratio; + + void *dest = cross_fade_buffer.Get(other_data.size); + memcpy(dest, other_data.data, other_data.size); + if (!pcm_mix(cross_fade_dither, dest, data.data, data.size, + in_audio_format.format, + mix_ratio)) + throw FormatRuntimeError("Cannot cross-fade format %s", + sample_format_to_string(in_audio_format.format)); + + data.data = dest; + data.size = other_data.size; + } + + /* apply filter chain */ + + return filter_instance->FilterPCM(data); +} diff --git a/src/output/Source.hxx b/src/output/Source.hxx new file mode 100644 index 000000000..e945b5602 --- /dev/null +++ b/src/output/Source.hxx @@ -0,0 +1,160 @@ +/* + * Copyright 2003-2016 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 AUDIO_OUTPUT_SOURCE_HXX +#define AUDIO_OUTPUT_SOURCE_HXX + +#include "check.h" +#include "Compiler.h" +#include "SharedPipeConsumer.hxx" +#include "AudioFormat.hxx" +#include "ReplayGainMode.hxx" +#include "pcm/PcmBuffer.hxx" +#include "pcm/PcmDither.hxx" + +#include + +template struct ConstBuffer; +struct MusicChunk; +class Filter; +class PreparedFilter; + +/** + * Source of audio data to be played by an #AudioOutput. It receives + * #MusicChunk instances from a #MusicPipe (via #SharedPipeConsumer). + * It applies configured filters, ReplayGain and returns plain PCM + * data. + */ +class AudioOutputSource { + /** + * The audio_format in which audio data is received from the + * player thread (which in turn receives it from the decoder). + */ + AudioFormat in_audio_format = AudioFormat::Undefined(); + + ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; + + /** + * A reference to the #MusicPipe and the current position. + */ + SharedPipeConsumer pipe; + + /** + * The serial number of the last replay gain info. 0 means no + * replay gain info was available. + */ + unsigned replay_gain_serial = 0; + + /** + * The serial number of the last replay gain info by the + * "other" chunk during cross-fading. + */ + unsigned other_replay_gain_serial = 0; + + /** + * The replay_gain_filter_plugin instance of this audio + * output. + */ + Filter *replay_gain_filter_instance = nullptr; + + /** + * The replay_gain_filter_plugin instance of this audio + * output, to be applied to the second chunk during + * cross-fading. + */ + Filter *other_replay_gain_filter_instance = nullptr; + + /** + * The buffer used to allocate the cross-fading result. + */ + PcmBuffer cross_fade_buffer; + + /** + * The dithering state for cross-fading two streams. + */ + PcmDither cross_fade_dither; + + /** + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. + */ + Filter *filter_instance = nullptr; + +public: + void SetReplayGainMode(ReplayGainMode _mode) { + replay_gain_mode = _mode; + } + + bool IsOpen() const { + return in_audio_format.IsDefined(); + } + + const AudioFormat &GetInputAudioFormat() const { + return in_audio_format; + } + + AudioFormat Open(AudioFormat audio_format, const MusicPipe &_pipe, + PreparedFilter *prepared_replay_gain_filter, + PreparedFilter *prepared_other_replay_gain_filter, + PreparedFilter *prepared_filter); + + void Close(); + + void Cancel() { + pipe.Cancel(); + } + + const MusicChunk *Get() { + assert(IsOpen()); + + return pipe.Get(); + } + + void Consume(const MusicChunk &chunk) { + assert(IsOpen()); + + pipe.Consume(chunk); + } + + bool IsChunkConsumed(const MusicChunk &chunk) const { + assert(IsOpen()); + + return pipe.IsConsumed(chunk); + } + + void ClearTailChunk(const MusicChunk &chunk) { + pipe.ClearTail(chunk); + } + + ConstBuffer FilterChunk(const MusicChunk &chunk); + +private: + void OpenFilter(AudioFormat audio_format, + PreparedFilter *prepared_replay_gain_filter, + PreparedFilter *prepared_other_replay_gain_filter, + PreparedFilter *prepared_filter); + + void CloseFilter(); + + ConstBuffer GetChunkData(const MusicChunk &chunk, + Filter *replay_gain_filter, + unsigned *replay_gain_serial_p); +}; + +#endif