input/cache: first draft of the file cache
This commit is contained in:
parent
e8a0ce643a
commit
5d74b5cee1
33
doc/user.rst
33
doc/user.rst
@ -334,6 +334,39 @@ The following table lists the input options valid for all plugins:
|
||||
|
||||
More information can be found in the :ref:`input_plugins` reference.
|
||||
|
||||
Configuring the Input Cache
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The input cache prefetches queued song files before they are going to
|
||||
be played. This has several advantages:
|
||||
|
||||
- risk of buffer underruns during playback is reduced because this
|
||||
decouples playback from disk (or network) I/O
|
||||
- bulk transfers may be faster and more energy efficient than loading
|
||||
small chunks on-the-fly
|
||||
- by prefetching several songs at a time, the hard disk can spin down
|
||||
for longer periods of time
|
||||
|
||||
This comes at a cost:
|
||||
|
||||
- memory usage
|
||||
- bulk transfers may reduce the performance of other applications
|
||||
which also want to access the disk (if the kernel's I/O scheduler
|
||||
isn't doing its job properly)
|
||||
|
||||
To enable the input cache, add an ``input_cache`` block to the
|
||||
configuration file:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
input_cache {
|
||||
size "1 GB"
|
||||
}
|
||||
|
||||
This allocates a cache of 1 GB. If the cache grows larger than that,
|
||||
older files will be evicted.
|
||||
|
||||
|
||||
Configuring decoder plugins
|
||||
---------------------------
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@ -23,6 +23,7 @@
|
||||
#include "Idle.hxx"
|
||||
#include "Stats.hxx"
|
||||
#include "client/List.hxx"
|
||||
#include "input/cache/Manager.hxx"
|
||||
|
||||
#ifdef ENABLE_CURL
|
||||
#include "RemoteTagCache.hxx"
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@ -54,6 +54,7 @@ struct Partition;
|
||||
class StateFile;
|
||||
class RemoteTagCache;
|
||||
class StickerDatabase;
|
||||
class InputCacheManager;
|
||||
|
||||
/**
|
||||
* A utility class which, when used as the first base class, ensures
|
||||
@ -98,6 +99,8 @@ struct Instance final
|
||||
Systemd::Watchdog systemd_watchdog;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<InputCacheManager> input_cache;
|
||||
|
||||
MaskMonitor idle_monitor;
|
||||
|
||||
#ifdef ENABLE_NEIGHBOR_PLUGINS
|
||||
|
@ -38,6 +38,8 @@
|
||||
#include "Log.hxx"
|
||||
#include "LogInit.hxx"
|
||||
#include "input/Init.hxx"
|
||||
#include "input/cache/Config.hxx"
|
||||
#include "input/cache/Manager.hxx"
|
||||
#include "event/Loop.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/Config.hxx"
|
||||
@ -414,6 +416,12 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
raw_config.GetPositive(ConfigOption::MAX_CONN, 10);
|
||||
instance.client_list = std::make_unique<ClientList>(max_clients);
|
||||
|
||||
const auto *input_cache_config = raw_config.GetBlock(ConfigBlockOption::INPUT_CACHE);
|
||||
if (input_cache_config != nullptr) {
|
||||
const InputCacheConfig c(*input_cache_config);
|
||||
instance.input_cache = std::make_unique<InputCacheManager>(c);
|
||||
}
|
||||
|
||||
initialize_decoder_and_player(instance,
|
||||
raw_config, config.replay_gain);
|
||||
|
||||
@ -461,6 +469,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
client_manager_init(raw_config);
|
||||
const ScopeInputPluginsInit input_plugins_init(raw_config,
|
||||
instance.io_thread.GetEventLoop());
|
||||
|
||||
const ScopePlaylistPluginsInit playlist_plugins_init(raw_config);
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
|
@ -20,10 +20,15 @@
|
||||
#include "config.h"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
#include "client/Listener.hxx"
|
||||
#include "input/cache/Manager.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
static constexpr Domain cache_domain("cache");
|
||||
|
||||
Partition::Partition(Instance &_instance,
|
||||
const char *_name,
|
||||
@ -37,7 +42,9 @@ Partition::Partition(Instance &_instance,
|
||||
global_events(instance.event_loop, BIND_THIS_METHOD(OnGlobalEvent)),
|
||||
playlist(max_length, *this),
|
||||
outputs(*this),
|
||||
pc(*this, outputs, buffer_chunks,
|
||||
pc(*this, outputs,
|
||||
instance.input_cache.get(),
|
||||
buffer_chunks,
|
||||
configured_audio_format, replay_gain_config)
|
||||
{
|
||||
UpdateEffectiveReplayGainMode();
|
||||
@ -51,6 +58,43 @@ Partition::EmitIdle(unsigned mask) noexcept
|
||||
instance.EmitIdle(mask);
|
||||
}
|
||||
|
||||
static void
|
||||
PrefetchSong(InputCacheManager &cache, const char *uri) noexcept
|
||||
{
|
||||
if (cache.Contains(uri))
|
||||
return;
|
||||
|
||||
FormatDebug(cache_domain, "Prefetch '%s'", uri);
|
||||
|
||||
try {
|
||||
cache.Prefetch(uri);
|
||||
} catch (...) {
|
||||
FormatError(std::current_exception(),
|
||||
"Prefetch '%s' failed", uri);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
PrefetchSong(InputCacheManager &cache, const DetachedSong &song) noexcept
|
||||
{
|
||||
PrefetchSong(cache, song.GetURI());
|
||||
}
|
||||
|
||||
inline void
|
||||
Partition::PrefetchQueue() noexcept
|
||||
{
|
||||
if (!instance.input_cache)
|
||||
return;
|
||||
|
||||
auto &cache = *instance.input_cache;
|
||||
|
||||
int next = playlist.GetNextPosition();
|
||||
if (next >= 0)
|
||||
PrefetchSong(cache, playlist.queue.Get(next));
|
||||
|
||||
// TODO: prefetch more songs
|
||||
}
|
||||
|
||||
void
|
||||
Partition::UpdateEffectiveReplayGainMode() noexcept
|
||||
{
|
||||
@ -106,6 +150,10 @@ void
|
||||
Partition::SyncWithPlayer() noexcept
|
||||
{
|
||||
playlist.SyncWithPlayer(pc);
|
||||
|
||||
/* TODO: invoke this function in batches, to let the hard disk
|
||||
spin down in between */
|
||||
PrefetchQueue();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -81,6 +81,14 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
||||
|
||||
void EmitIdle(unsigned mask) noexcept;
|
||||
|
||||
/**
|
||||
* Populate the #InputCacheManager with soon-to-be-played song
|
||||
* files.
|
||||
*
|
||||
* Errors will be logged.
|
||||
*/
|
||||
void PrefetchQueue() noexcept;
|
||||
|
||||
void ClearQueue() noexcept {
|
||||
playlist.Clear(pc);
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ enum class ConfigBlockOption {
|
||||
AUDIO_OUTPUT,
|
||||
DECODER,
|
||||
INPUT,
|
||||
INPUT_CACHE,
|
||||
PLAYLIST_PLUGIN,
|
||||
RESAMPLER,
|
||||
AUDIO_FILTER,
|
||||
|
@ -86,6 +86,7 @@ const ConfigTemplate config_block_templates[] = {
|
||||
{ "audio_output", true },
|
||||
{ "decoder", true },
|
||||
{ "input", true },
|
||||
{ "input_cache" },
|
||||
{ "playlist_plugin", true },
|
||||
{ "resampler" },
|
||||
{ "filter", true },
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "Log.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "input/LocalOpen.hxx"
|
||||
#include "input/cache/Manager.hxx"
|
||||
#include "input/cache/Stream.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
@ -52,8 +54,18 @@ DecoderBridge::~DecoderBridge() noexcept
|
||||
}
|
||||
|
||||
InputStreamPtr
|
||||
DecoderBridge::OpenLocal(Path path_fs)
|
||||
DecoderBridge::OpenLocal(Path path_fs, const char *uri_utf8)
|
||||
{
|
||||
if (dc.input_cache != nullptr) {
|
||||
auto lease = dc.input_cache->Get(uri_utf8, true);
|
||||
if (lease) {
|
||||
auto is = std::make_unique<CacheInputStream>(std::move(lease),
|
||||
dc.mutex);
|
||||
is->SetHandler(&dc);
|
||||
return is;
|
||||
}
|
||||
}
|
||||
|
||||
return OpenLocalInputStream(path_fs, dc.mutex);
|
||||
}
|
||||
|
||||
|
@ -157,7 +157,7 @@ public:
|
||||
/**
|
||||
* Open a local file.
|
||||
*/
|
||||
InputStreamPtr OpenLocal(Path path_fs);
|
||||
InputStreamPtr OpenLocal(Path path_fs, const char *uri_utf8);
|
||||
|
||||
/* virtual methods from DecoderClient */
|
||||
void Ready(AudioFormat audio_format,
|
||||
|
@ -26,9 +26,11 @@
|
||||
#include <assert.h>
|
||||
|
||||
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
|
||||
InputCacheManager *_input_cache,
|
||||
const AudioFormat _configured_audio_format,
|
||||
const ReplayGainConfig &_replay_gain_config) noexcept
|
||||
:thread(BIND_THIS_METHOD(RunThread)),
|
||||
input_cache(_input_cache),
|
||||
mutex(_mutex), client_cond(_client_cond),
|
||||
configured_audio_format(_configured_audio_format),
|
||||
replay_gain_config(_replay_gain_config) {}
|
||||
|
@ -46,6 +46,7 @@
|
||||
class DetachedSong;
|
||||
class MusicBuffer;
|
||||
class MusicPipe;
|
||||
class InputCacheManager;
|
||||
|
||||
enum class DecoderState : uint8_t {
|
||||
STOP = 0,
|
||||
@ -68,6 +69,8 @@ class DecoderControl final : public InputStreamHandler {
|
||||
Thread thread;
|
||||
|
||||
public:
|
||||
InputCacheManager *const input_cache;
|
||||
|
||||
/**
|
||||
* This lock protects #state and #command.
|
||||
*
|
||||
@ -181,6 +184,7 @@ public:
|
||||
* @param _client_cond see #client_cond
|
||||
*/
|
||||
DecoderControl(Mutex &_mutex, Cond &_client_cond,
|
||||
InputCacheManager *_input_cache,
|
||||
const AudioFormat _configured_audio_format,
|
||||
const ReplayGainConfig &_replay_gain_config) noexcept;
|
||||
~DecoderControl() noexcept;
|
||||
|
@ -346,7 +346,7 @@ decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs)
|
||||
InputStreamPtr input_stream;
|
||||
|
||||
try {
|
||||
input_stream = bridge.OpenLocal(path_fs);
|
||||
input_stream = bridge.OpenLocal(path_fs, uri_utf8);
|
||||
} catch (const std::system_error &e) {
|
||||
if (IsPathNotFound(e) &&
|
||||
/* ENOTDIR means this may be a path inside a
|
||||
|
@ -152,6 +152,18 @@ BufferingInputStream::RunThreadLocked(std::unique_lock<Mutex> &lock)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* enforce an upper limit for each
|
||||
InputStream::Read() call; this is necessary
|
||||
for plugins which are unable to do partial
|
||||
reads, e.g. when reading local files, the
|
||||
read() system call will not return until
|
||||
all requested bytes have been read from the
|
||||
hard disk, instead of returning when "some"
|
||||
data has been read */
|
||||
constexpr size_t MAX_READ = 64 * 1024;
|
||||
if (w.size > MAX_READ)
|
||||
w.size = MAX_READ;
|
||||
|
||||
size_t nbytes = input->Read(lock, w.data, w.size);
|
||||
buffer.Commit(read_offset, read_offset + nbytes);
|
||||
|
||||
|
35
src/input/cache/Config.cxx
vendored
Normal file
35
src/input/cache/Config.cxx
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "config/Parser.hxx"
|
||||
|
||||
static constexpr size_t KILOBYTE = 1024;
|
||||
static constexpr size_t MEGABYTE = 1024 * KILOBYTE;
|
||||
|
||||
InputCacheConfig::InputCacheConfig(const ConfigBlock &block)
|
||||
{
|
||||
size = 256 * MEGABYTE;
|
||||
const auto *size_param = block.GetBlockParam("size");
|
||||
if (size_param != nullptr)
|
||||
size = size_param->With([](const char *s){
|
||||
return ParseSize(s);
|
||||
});
|
||||
}
|
33
src/input/cache/Config.hxx
vendored
Normal file
33
src/input/cache/Config.hxx
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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_INPUT_CACHE_CONFIG_HXX
|
||||
#define MPD_INPUT_CACHE_CONFIG_HXX
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct ConfigBlock;
|
||||
|
||||
struct InputCacheConfig {
|
||||
size_t size;
|
||||
|
||||
explicit InputCacheConfig(const ConfigBlock &block);
|
||||
};
|
||||
|
||||
#endif
|
62
src/input/cache/Item.cxx
vendored
Normal file
62
src/input/cache/Item.cxx
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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 "Item.hxx"
|
||||
#include "Lease.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
InputCacheItem::InputCacheItem(InputStreamPtr _input) noexcept
|
||||
:BufferingInputStream(std::move(_input)),
|
||||
uri(GetInput().GetURI())
|
||||
{
|
||||
}
|
||||
|
||||
InputCacheItem::~InputCacheItem() noexcept
|
||||
{
|
||||
assert(leases.empty());
|
||||
}
|
||||
|
||||
void
|
||||
InputCacheItem::AddLease(InputCacheLease &lease) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
leases.push_back(lease);
|
||||
}
|
||||
|
||||
void
|
||||
InputCacheItem::RemoveLease(InputCacheLease &lease) noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
auto i = leases.iterator_to(lease);
|
||||
if (i == next_lease)
|
||||
++next_lease;
|
||||
leases.erase(i);
|
||||
|
||||
// TODO: ensure that OnBufferAvailable() isn't currently running
|
||||
}
|
||||
|
||||
void
|
||||
InputCacheItem::OnBufferAvailable() noexcept
|
||||
{
|
||||
for (auto i = leases.begin(); i != leases.end(); i = next_lease) {
|
||||
next_lease = std::next(i);
|
||||
i->OnInputCacheAvailable();
|
||||
}
|
||||
}
|
80
src/input/cache/Item.hxx
vendored
Normal file
80
src/input/cache/Item.hxx
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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_INPUT_CACHE_ITEM_HXX
|
||||
#define MPD_INPUT_CACHE_ITEM_HXX
|
||||
|
||||
#include "input/BufferingInputStream.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "util/SparseBuffer.hxx"
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/intrusive/set_hook.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class InputCacheLease;
|
||||
|
||||
/**
|
||||
* An item in the #InputCacheManager. It caches the contents of a
|
||||
* file, and reading and managing it through the base class
|
||||
* #BufferingInputStream.
|
||||
*
|
||||
* Use the class #CacheInputStream to read from it.
|
||||
*/
|
||||
class InputCacheItem final
|
||||
: public BufferingInputStream,
|
||||
public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink>>,
|
||||
public boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>
|
||||
{
|
||||
const std::string uri;
|
||||
|
||||
using LeaseList =
|
||||
boost::intrusive::list<InputCacheLease,
|
||||
boost::intrusive::base_hook<boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>>,
|
||||
boost::intrusive::constant_time_size<false>>;
|
||||
|
||||
LeaseList leases;
|
||||
LeaseList::iterator next_lease = leases.end();
|
||||
|
||||
public:
|
||||
explicit InputCacheItem(InputStreamPtr _input) noexcept;
|
||||
~InputCacheItem() noexcept;
|
||||
|
||||
const char *GetUri() const noexcept {
|
||||
return uri.c_str();
|
||||
}
|
||||
|
||||
using BufferingInputStream::size;
|
||||
|
||||
bool IsInUse() const noexcept {
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
return !leases.empty();
|
||||
}
|
||||
|
||||
void AddLease(InputCacheLease &lease) noexcept;
|
||||
void RemoveLease(InputCacheLease &lease) noexcept;
|
||||
|
||||
private:
|
||||
/* virtual methods from class BufferingInputStream */
|
||||
void OnBufferAvailable() noexcept override;
|
||||
};
|
||||
|
||||
#endif
|
94
src/input/cache/Lease.hxx
vendored
Normal file
94
src/input/cache/Lease.hxx
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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_INPUT_CACHE_LEASE_HXX
|
||||
#define MPD_INPUT_CACHE_LEASE_HXX
|
||||
|
||||
#include "Item.hxx"
|
||||
|
||||
#include <boost/intrusive/list_hook.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* A lease for an #InputCacheItem.
|
||||
*/
|
||||
class InputCacheLease
|
||||
: public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>
|
||||
{
|
||||
InputCacheItem *item = nullptr;
|
||||
|
||||
public:
|
||||
InputCacheLease() = default;
|
||||
|
||||
explicit InputCacheLease(InputCacheItem &_item) noexcept
|
||||
:item(&_item)
|
||||
{
|
||||
item->AddLease(*this);
|
||||
}
|
||||
|
||||
InputCacheLease(InputCacheLease &&src) noexcept
|
||||
:item(std::exchange(src.item, nullptr))
|
||||
{
|
||||
if (item != nullptr) {
|
||||
item->RemoveLease(src);
|
||||
item->AddLease(*this);
|
||||
}
|
||||
}
|
||||
|
||||
~InputCacheLease() noexcept {
|
||||
if (item != nullptr)
|
||||
item->RemoveLease(*this);
|
||||
}
|
||||
|
||||
InputCacheLease &operator=(InputCacheLease &&src) noexcept {
|
||||
using std::swap;
|
||||
swap(item, src.item);
|
||||
|
||||
if (item != nullptr) {
|
||||
item->RemoveLease(src);
|
||||
item->AddLease(*this);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const noexcept {
|
||||
return item != nullptr;
|
||||
}
|
||||
|
||||
auto &operator*() const noexcept {
|
||||
return *item;
|
||||
}
|
||||
|
||||
auto *operator->() const noexcept {
|
||||
return item;
|
||||
}
|
||||
|
||||
auto &GetCacheItem() const noexcept {
|
||||
return *item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller locks #InputCacheItem::mutex.
|
||||
*/
|
||||
virtual void OnInputCacheAvailable() noexcept {}
|
||||
};
|
||||
|
||||
#endif
|
163
src/input/cache/Manager.cxx
vendored
Normal file
163
src/input/cache/Manager.cxx
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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 "Manager.hxx"
|
||||
#include "Config.hxx"
|
||||
#include "Item.hxx"
|
||||
#include "Lease.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "util/DeleteDisposer.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
inline bool
|
||||
InputCacheManager::ItemCompare::operator()(const InputCacheItem &a,
|
||||
const char *b) const noexcept
|
||||
{
|
||||
return strcmp(a.GetUri(), b) < 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
InputCacheManager::ItemCompare::operator()(const char *a,
|
||||
const InputCacheItem &b) const noexcept
|
||||
{
|
||||
return strcmp(a, b.GetUri()) < 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
InputCacheManager::ItemCompare::operator()(const InputCacheItem &a,
|
||||
const InputCacheItem &b) const noexcept
|
||||
{
|
||||
return strcmp(a.GetUri(), b.GetUri()) < 0;
|
||||
}
|
||||
|
||||
InputCacheManager::InputCacheManager(const InputCacheConfig &config) noexcept
|
||||
:max_total_size(config.size)
|
||||
{
|
||||
}
|
||||
|
||||
InputCacheManager::~InputCacheManager() noexcept
|
||||
{
|
||||
items_by_time.clear_and_dispose(DeleteDisposer());
|
||||
}
|
||||
|
||||
bool
|
||||
InputCacheManager::IsEligible(const InputStream &input) noexcept
|
||||
{
|
||||
assert(input.IsReady());
|
||||
|
||||
return input.IsSeekable() && input.KnownSize() &&
|
||||
input.GetSize() > 0 &&
|
||||
input.GetSize() <= max_total_size / 2;
|
||||
}
|
||||
|
||||
bool
|
||||
InputCacheManager::Contains(const char *uri) noexcept
|
||||
{
|
||||
return Get(uri, false);
|
||||
}
|
||||
|
||||
InputCacheLease
|
||||
InputCacheManager::Get(const char *uri, bool create)
|
||||
{
|
||||
// TODO: allow caching remote files
|
||||
if (!PathTraitsUTF8::IsAbsolute(uri))
|
||||
return {};
|
||||
|
||||
UriMap::insert_commit_data hint;
|
||||
auto result = items_by_uri.insert_check(uri, items_by_uri.key_comp(),
|
||||
hint);
|
||||
if (!result.second) {
|
||||
auto &item = *result.first;
|
||||
|
||||
/* refresh */
|
||||
items_by_time.erase(items_by_time.iterator_to(item));
|
||||
items_by_time.push_back(item);
|
||||
|
||||
// TODO revalidate the cache item using the file's mtime?
|
||||
// TODO if cache item contains error, retry now?
|
||||
|
||||
return InputCacheLease(item);
|
||||
}
|
||||
|
||||
if (!create)
|
||||
return {};
|
||||
|
||||
// TODO: wait for "ready" without blocking here
|
||||
auto is = InputStream::OpenReady(uri, mutex);
|
||||
|
||||
if (!IsEligible(*is))
|
||||
return {};
|
||||
|
||||
const size_t size = is->GetSize();
|
||||
total_size += size;
|
||||
|
||||
while (total_size > max_total_size && EvictOldestUnused()) {}
|
||||
|
||||
auto *item = new InputCacheItem(std::move(is));
|
||||
items_by_uri.insert_commit(*item, hint);
|
||||
items_by_time.push_back(*item);
|
||||
|
||||
return InputCacheLease(*item);
|
||||
}
|
||||
|
||||
void
|
||||
InputCacheManager::Prefetch(const char *uri)
|
||||
{
|
||||
Get(uri, true);
|
||||
}
|
||||
|
||||
void
|
||||
InputCacheManager::Remove(InputCacheItem &item) noexcept
|
||||
{
|
||||
assert(total_size >= item.size());
|
||||
total_size -= item.size();
|
||||
|
||||
items_by_time.erase(items_by_time.iterator_to(item));
|
||||
items_by_uri.erase(items_by_uri.iterator_to(item));
|
||||
}
|
||||
|
||||
void
|
||||
InputCacheManager::Delete(InputCacheItem *item) noexcept
|
||||
{
|
||||
Remove(*item);
|
||||
delete item;
|
||||
}
|
||||
|
||||
InputCacheItem *
|
||||
InputCacheManager::FindOldestUnused() noexcept
|
||||
{
|
||||
for (auto &i : items_by_time)
|
||||
if (!i.IsInUse())
|
||||
return &i;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
InputCacheManager::EvictOldestUnused() noexcept
|
||||
{
|
||||
auto *item = FindOldestUnused();
|
||||
if (item == nullptr)
|
||||
return false;
|
||||
|
||||
Delete(item);
|
||||
return true;
|
||||
}
|
114
src/input/cache/Manager.hxx
vendored
Normal file
114
src/input/cache/Manager.hxx
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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_INPUT_CACHE_MANAGER_HXX
|
||||
#define MPD_INPUT_CACHE_MANAGER_HXX
|
||||
|
||||
#include "input/Offset.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <boost/intrusive/set.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
class InputStream;
|
||||
class InputCacheItem;
|
||||
class InputCacheLease;
|
||||
struct InputCacheConfig;
|
||||
|
||||
/**
|
||||
* A class which caches files in RAM. It is supposed to prefetch
|
||||
* files before they are played.
|
||||
*/
|
||||
class InputCacheManager {
|
||||
const size_t max_total_size;
|
||||
|
||||
mutable Mutex mutex;
|
||||
|
||||
size_t total_size = 0;
|
||||
|
||||
struct ItemCompare {
|
||||
gcc_pure
|
||||
bool operator()(const InputCacheItem &a,
|
||||
const char *b) const noexcept;
|
||||
|
||||
gcc_pure
|
||||
bool operator()(const char *a,
|
||||
const InputCacheItem &b) const noexcept;
|
||||
|
||||
gcc_pure
|
||||
bool operator()(const InputCacheItem &a,
|
||||
const InputCacheItem &b) const noexcept;
|
||||
};
|
||||
|
||||
boost::intrusive::list<InputCacheItem,
|
||||
boost::intrusive::base_hook<boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink>>>,
|
||||
boost::intrusive::constant_time_size<false>> items_by_time;
|
||||
|
||||
using UriMap =
|
||||
boost::intrusive::set<InputCacheItem,
|
||||
boost::intrusive::base_hook<boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>>,
|
||||
boost::intrusive::compare<ItemCompare>,
|
||||
boost::intrusive::constant_time_size<false>>;
|
||||
|
||||
UriMap items_by_uri;
|
||||
|
||||
public:
|
||||
explicit InputCacheManager(const InputCacheConfig &config) noexcept;
|
||||
~InputCacheManager() noexcept;
|
||||
|
||||
gcc_pure
|
||||
bool Contains(const char *uri) noexcept;
|
||||
|
||||
/**
|
||||
* Throws if opening the #InputStream fails.
|
||||
*
|
||||
* @param create if true, then the cache item will be created
|
||||
* if it did not exist
|
||||
* @return a lease of the new item or nullptr if the file is
|
||||
* not eligible for caching
|
||||
*/
|
||||
InputCacheLease Get(const char *uri, bool create);
|
||||
|
||||
/**
|
||||
* Shortcut for "Get(uri,true)", discarding the returned
|
||||
* lease.
|
||||
*/
|
||||
void Prefetch(const char *uri);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Check whether the given #InputStream can be stored in this
|
||||
* cache.
|
||||
*/
|
||||
bool IsEligible(const InputStream &input) noexcept;
|
||||
|
||||
void Remove(InputCacheItem &item) noexcept;
|
||||
void Delete(InputCacheItem *item) noexcept;
|
||||
|
||||
InputCacheItem *FindOldestUnused() noexcept;
|
||||
|
||||
/**
|
||||
* @return true if one item has been evicted, false if no
|
||||
* unused item was found
|
||||
*/
|
||||
bool EvictOldestUnused() noexcept;
|
||||
};
|
||||
|
||||
#endif
|
96
src/input/cache/Stream.cxx
vendored
Normal file
96
src/input/cache/Stream.cxx
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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 "Stream.hxx"
|
||||
|
||||
CacheInputStream::CacheInputStream(InputCacheLease _lease,
|
||||
Mutex &_mutex) noexcept
|
||||
:InputStream(_lease->GetUri(), _mutex),
|
||||
InputCacheLease(std::move(_lease))
|
||||
{
|
||||
auto &i = GetCacheItem();
|
||||
size = i.size();
|
||||
seekable = true;
|
||||
SetReady();
|
||||
}
|
||||
|
||||
void
|
||||
CacheInputStream::Check()
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
auto &i = GetCacheItem();
|
||||
const std::lock_guard<Mutex> protect(i.mutex);
|
||||
|
||||
i.Check();
|
||||
}
|
||||
|
||||
void
|
||||
CacheInputStream::Seek(std::unique_lock<Mutex> &, offset_type new_offset)
|
||||
{
|
||||
offset = new_offset;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheInputStream::IsEOF() const noexcept
|
||||
{
|
||||
return offset == size;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheInputStream::IsAvailable() const noexcept
|
||||
{
|
||||
const auto _offset = offset;
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
auto &i = GetCacheItem();
|
||||
const std::lock_guard<Mutex> protect(i.mutex);
|
||||
|
||||
return i.IsAvailable(_offset);
|
||||
}
|
||||
|
||||
size_t
|
||||
CacheInputStream::Read(std::unique_lock<Mutex> &lock,
|
||||
void *ptr, size_t read_size)
|
||||
{
|
||||
const auto _offset = offset;
|
||||
auto &i = GetCacheItem();
|
||||
|
||||
size_t nbytes;
|
||||
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
const std::lock_guard<Mutex> protect(i.mutex);
|
||||
|
||||
nbytes = i.Read(lock, _offset, ptr, read_size);
|
||||
}
|
||||
|
||||
offset += nbytes;
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
void
|
||||
CacheInputStream::OnInputCacheAvailable() noexcept
|
||||
{
|
||||
auto &i = GetCacheItem();
|
||||
const ScopeUnlock unlock(i.mutex);
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
InvokeOnAvailable();
|
||||
}
|
52
src/input/cache/Stream.hxx
vendored
Normal file
52
src/input/cache/Stream.hxx
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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_CACHE_INPUT_STREAM_HXX
|
||||
#define MPD_CACHE_INPUT_STREAM_HXX
|
||||
|
||||
#include "Lease.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
|
||||
/**
|
||||
* An #InputStream implementation which reads data from an
|
||||
* #InputCacheItem.
|
||||
*/
|
||||
class CacheInputStream final : public InputStream, InputCacheLease {
|
||||
public:
|
||||
CacheInputStream(InputCacheLease _lease, Mutex &_mutex) noexcept;
|
||||
|
||||
/* virtual methods from class InputStream */
|
||||
void Check() override;
|
||||
/* we don't need to implement Update() because all attributes
|
||||
have been copied already in our constructor */
|
||||
//void Update() noexcept;
|
||||
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
|
||||
bool IsEOF() const noexcept override;
|
||||
/* we don't support tags */
|
||||
// std::unique_ptr<Tag> ReadTag() override;
|
||||
bool IsAvailable() const noexcept override;
|
||||
size_t Read(std::unique_lock<Mutex> &lock,
|
||||
void *ptr, size_t size) override;
|
||||
|
||||
private:
|
||||
/* virtual methods from class InputCacheLease */
|
||||
void OnInputCacheAvailable() noexcept override;
|
||||
};
|
||||
|
||||
#endif
|
@ -35,6 +35,10 @@ input_glue = static_library(
|
||||
'BufferingInputStream.cxx',
|
||||
'BufferedInputStream.cxx',
|
||||
'MaybeBufferedInputStream.cxx',
|
||||
'cache/Config.cxx',
|
||||
'cache/Manager.cxx',
|
||||
'cache/Item.cxx',
|
||||
'cache/Stream.cxx',
|
||||
include_directories: inc,
|
||||
)
|
||||
|
||||
|
@ -28,10 +28,12 @@
|
||||
|
||||
PlayerControl::PlayerControl(PlayerListener &_listener,
|
||||
PlayerOutputs &_outputs,
|
||||
InputCacheManager *_input_cache,
|
||||
unsigned _buffer_chunks,
|
||||
AudioFormat _configured_audio_format,
|
||||
const ReplayGainConfig &_replay_gain_config) noexcept
|
||||
:listener(_listener), outputs(_outputs),
|
||||
input_cache(_input_cache),
|
||||
buffer_chunks(_buffer_chunks),
|
||||
configured_audio_format(_configured_audio_format),
|
||||
thread(BIND_THIS_METHOD(RunThread)),
|
||||
|
@ -39,6 +39,7 @@
|
||||
struct Tag;
|
||||
class PlayerListener;
|
||||
class PlayerOutputs;
|
||||
class InputCacheManager;
|
||||
class DetachedSong;
|
||||
|
||||
enum class PlayerState : uint8_t {
|
||||
@ -116,6 +117,8 @@ class PlayerControl final : public AudioOutputClient {
|
||||
|
||||
PlayerOutputs &outputs;
|
||||
|
||||
InputCacheManager *const input_cache;
|
||||
|
||||
const unsigned buffer_chunks;
|
||||
|
||||
/**
|
||||
@ -234,6 +237,7 @@ class PlayerControl final : public AudioOutputClient {
|
||||
public:
|
||||
PlayerControl(PlayerListener &_listener,
|
||||
PlayerOutputs &_outputs,
|
||||
InputCacheManager *_input_cache,
|
||||
unsigned buffer_chunks,
|
||||
AudioFormat _configured_audio_format,
|
||||
const ReplayGainConfig &_replay_gain_config) noexcept;
|
||||
|
@ -1132,6 +1132,7 @@ try {
|
||||
SetThreadName("player");
|
||||
|
||||
DecoderControl dc(mutex, cond,
|
||||
input_cache,
|
||||
configured_audio_format,
|
||||
replay_gain_config);
|
||||
dc.StartThread();
|
||||
|
Loading…
Reference in New Issue
Block a user