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.
|
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
|
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
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@ -23,6 +23,7 @@
|
|||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
#include "Stats.hxx"
|
#include "Stats.hxx"
|
||||||
#include "client/List.hxx"
|
#include "client/List.hxx"
|
||||||
|
#include "input/cache/Manager.hxx"
|
||||||
|
|
||||||
#ifdef ENABLE_CURL
|
#ifdef ENABLE_CURL
|
||||||
#include "RemoteTagCache.hxx"
|
#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
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@ -54,6 +54,7 @@ struct Partition;
|
|||||||
class StateFile;
|
class StateFile;
|
||||||
class RemoteTagCache;
|
class RemoteTagCache;
|
||||||
class StickerDatabase;
|
class StickerDatabase;
|
||||||
|
class InputCacheManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class which, when used as the first base class, ensures
|
* A utility class which, when used as the first base class, ensures
|
||||||
@ -98,6 +99,8 @@ struct Instance final
|
|||||||
Systemd::Watchdog systemd_watchdog;
|
Systemd::Watchdog systemd_watchdog;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
std::unique_ptr<InputCacheManager> input_cache;
|
||||||
|
|
||||||
MaskMonitor idle_monitor;
|
MaskMonitor idle_monitor;
|
||||||
|
|
||||||
#ifdef ENABLE_NEIGHBOR_PLUGINS
|
#ifdef ENABLE_NEIGHBOR_PLUGINS
|
||||||
|
@ -38,6 +38,8 @@
|
|||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "LogInit.hxx"
|
#include "LogInit.hxx"
|
||||||
#include "input/Init.hxx"
|
#include "input/Init.hxx"
|
||||||
|
#include "input/cache/Config.hxx"
|
||||||
|
#include "input/cache/Manager.hxx"
|
||||||
#include "event/Loop.hxx"
|
#include "event/Loop.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/Config.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);
|
raw_config.GetPositive(ConfigOption::MAX_CONN, 10);
|
||||||
instance.client_list = std::make_unique<ClientList>(max_clients);
|
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,
|
initialize_decoder_and_player(instance,
|
||||||
raw_config, config.replay_gain);
|
raw_config, config.replay_gain);
|
||||||
|
|
||||||
@ -461,6 +469,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
|||||||
client_manager_init(raw_config);
|
client_manager_init(raw_config);
|
||||||
const ScopeInputPluginsInit input_plugins_init(raw_config,
|
const ScopeInputPluginsInit input_plugins_init(raw_config,
|
||||||
instance.io_thread.GetEventLoop());
|
instance.io_thread.GetEventLoop());
|
||||||
|
|
||||||
const ScopePlaylistPluginsInit playlist_plugins_init(raw_config);
|
const ScopePlaylistPluginsInit playlist_plugins_init(raw_config);
|
||||||
|
|
||||||
#ifdef ENABLE_DAEMON
|
#ifdef ENABLE_DAEMON
|
||||||
|
@ -20,10 +20,15 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
|
#include "Log.hxx"
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
#include "mixer/Volume.hxx"
|
#include "mixer/Volume.hxx"
|
||||||
#include "IdleFlags.hxx"
|
#include "IdleFlags.hxx"
|
||||||
#include "client/Listener.hxx"
|
#include "client/Listener.hxx"
|
||||||
|
#include "input/cache/Manager.hxx"
|
||||||
|
#include "util/Domain.hxx"
|
||||||
|
|
||||||
|
static constexpr Domain cache_domain("cache");
|
||||||
|
|
||||||
Partition::Partition(Instance &_instance,
|
Partition::Partition(Instance &_instance,
|
||||||
const char *_name,
|
const char *_name,
|
||||||
@ -37,7 +42,9 @@ Partition::Partition(Instance &_instance,
|
|||||||
global_events(instance.event_loop, BIND_THIS_METHOD(OnGlobalEvent)),
|
global_events(instance.event_loop, BIND_THIS_METHOD(OnGlobalEvent)),
|
||||||
playlist(max_length, *this),
|
playlist(max_length, *this),
|
||||||
outputs(*this),
|
outputs(*this),
|
||||||
pc(*this, outputs, buffer_chunks,
|
pc(*this, outputs,
|
||||||
|
instance.input_cache.get(),
|
||||||
|
buffer_chunks,
|
||||||
configured_audio_format, replay_gain_config)
|
configured_audio_format, replay_gain_config)
|
||||||
{
|
{
|
||||||
UpdateEffectiveReplayGainMode();
|
UpdateEffectiveReplayGainMode();
|
||||||
@ -51,6 +58,43 @@ Partition::EmitIdle(unsigned mask) noexcept
|
|||||||
instance.EmitIdle(mask);
|
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
|
void
|
||||||
Partition::UpdateEffectiveReplayGainMode() noexcept
|
Partition::UpdateEffectiveReplayGainMode() noexcept
|
||||||
{
|
{
|
||||||
@ -106,6 +150,10 @@ void
|
|||||||
Partition::SyncWithPlayer() noexcept
|
Partition::SyncWithPlayer() noexcept
|
||||||
{
|
{
|
||||||
playlist.SyncWithPlayer(pc);
|
playlist.SyncWithPlayer(pc);
|
||||||
|
|
||||||
|
/* TODO: invoke this function in batches, to let the hard disk
|
||||||
|
spin down in between */
|
||||||
|
PrefetchQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -81,6 +81,14 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
|||||||
|
|
||||||
void EmitIdle(unsigned mask) noexcept;
|
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 {
|
void ClearQueue() noexcept {
|
||||||
playlist.Clear(pc);
|
playlist.Clear(pc);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ enum class ConfigBlockOption {
|
|||||||
AUDIO_OUTPUT,
|
AUDIO_OUTPUT,
|
||||||
DECODER,
|
DECODER,
|
||||||
INPUT,
|
INPUT,
|
||||||
|
INPUT_CACHE,
|
||||||
PLAYLIST_PLUGIN,
|
PLAYLIST_PLUGIN,
|
||||||
RESAMPLER,
|
RESAMPLER,
|
||||||
AUDIO_FILTER,
|
AUDIO_FILTER,
|
||||||
|
@ -86,6 +86,7 @@ const ConfigTemplate config_block_templates[] = {
|
|||||||
{ "audio_output", true },
|
{ "audio_output", true },
|
||||||
{ "decoder", true },
|
{ "decoder", true },
|
||||||
{ "input", true },
|
{ "input", true },
|
||||||
|
{ "input_cache" },
|
||||||
{ "playlist_plugin", true },
|
{ "playlist_plugin", true },
|
||||||
{ "resampler" },
|
{ "resampler" },
|
||||||
{ "filter", true },
|
{ "filter", true },
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "input/LocalOpen.hxx"
|
#include "input/LocalOpen.hxx"
|
||||||
|
#include "input/cache/Manager.hxx"
|
||||||
|
#include "input/cache/Stream.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
@ -52,8 +54,18 @@ DecoderBridge::~DecoderBridge() noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
InputStreamPtr
|
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);
|
return OpenLocalInputStream(path_fs, dc.mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Open a local file.
|
* Open a local file.
|
||||||
*/
|
*/
|
||||||
InputStreamPtr OpenLocal(Path path_fs);
|
InputStreamPtr OpenLocal(Path path_fs, const char *uri_utf8);
|
||||||
|
|
||||||
/* virtual methods from DecoderClient */
|
/* virtual methods from DecoderClient */
|
||||||
void Ready(AudioFormat audio_format,
|
void Ready(AudioFormat audio_format,
|
||||||
|
@ -26,9 +26,11 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
|
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
|
||||||
|
InputCacheManager *_input_cache,
|
||||||
const AudioFormat _configured_audio_format,
|
const AudioFormat _configured_audio_format,
|
||||||
const ReplayGainConfig &_replay_gain_config) noexcept
|
const ReplayGainConfig &_replay_gain_config) noexcept
|
||||||
:thread(BIND_THIS_METHOD(RunThread)),
|
:thread(BIND_THIS_METHOD(RunThread)),
|
||||||
|
input_cache(_input_cache),
|
||||||
mutex(_mutex), client_cond(_client_cond),
|
mutex(_mutex), client_cond(_client_cond),
|
||||||
configured_audio_format(_configured_audio_format),
|
configured_audio_format(_configured_audio_format),
|
||||||
replay_gain_config(_replay_gain_config) {}
|
replay_gain_config(_replay_gain_config) {}
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
class DetachedSong;
|
class DetachedSong;
|
||||||
class MusicBuffer;
|
class MusicBuffer;
|
||||||
class MusicPipe;
|
class MusicPipe;
|
||||||
|
class InputCacheManager;
|
||||||
|
|
||||||
enum class DecoderState : uint8_t {
|
enum class DecoderState : uint8_t {
|
||||||
STOP = 0,
|
STOP = 0,
|
||||||
@ -68,6 +69,8 @@ class DecoderControl final : public InputStreamHandler {
|
|||||||
Thread thread;
|
Thread thread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
InputCacheManager *const input_cache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This lock protects #state and #command.
|
* This lock protects #state and #command.
|
||||||
*
|
*
|
||||||
@ -181,6 +184,7 @@ public:
|
|||||||
* @param _client_cond see #client_cond
|
* @param _client_cond see #client_cond
|
||||||
*/
|
*/
|
||||||
DecoderControl(Mutex &_mutex, Cond &_client_cond,
|
DecoderControl(Mutex &_mutex, Cond &_client_cond,
|
||||||
|
InputCacheManager *_input_cache,
|
||||||
const AudioFormat _configured_audio_format,
|
const AudioFormat _configured_audio_format,
|
||||||
const ReplayGainConfig &_replay_gain_config) noexcept;
|
const ReplayGainConfig &_replay_gain_config) noexcept;
|
||||||
~DecoderControl() noexcept;
|
~DecoderControl() noexcept;
|
||||||
|
@ -346,7 +346,7 @@ decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs)
|
|||||||
InputStreamPtr input_stream;
|
InputStreamPtr input_stream;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
input_stream = bridge.OpenLocal(path_fs);
|
input_stream = bridge.OpenLocal(path_fs, uri_utf8);
|
||||||
} catch (const std::system_error &e) {
|
} catch (const std::system_error &e) {
|
||||||
if (IsPathNotFound(e) &&
|
if (IsPathNotFound(e) &&
|
||||||
/* ENOTDIR means this may be a path inside a
|
/* ENOTDIR means this may be a path inside a
|
||||||
|
@ -152,6 +152,18 @@ BufferingInputStream::RunThreadLocked(std::unique_lock<Mutex> &lock)
|
|||||||
continue;
|
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);
|
size_t nbytes = input->Read(lock, w.data, w.size);
|
||||||
buffer.Commit(read_offset, read_offset + nbytes);
|
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',
|
'BufferingInputStream.cxx',
|
||||||
'BufferedInputStream.cxx',
|
'BufferedInputStream.cxx',
|
||||||
'MaybeBufferedInputStream.cxx',
|
'MaybeBufferedInputStream.cxx',
|
||||||
|
'cache/Config.cxx',
|
||||||
|
'cache/Manager.cxx',
|
||||||
|
'cache/Item.cxx',
|
||||||
|
'cache/Stream.cxx',
|
||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,10 +28,12 @@
|
|||||||
|
|
||||||
PlayerControl::PlayerControl(PlayerListener &_listener,
|
PlayerControl::PlayerControl(PlayerListener &_listener,
|
||||||
PlayerOutputs &_outputs,
|
PlayerOutputs &_outputs,
|
||||||
|
InputCacheManager *_input_cache,
|
||||||
unsigned _buffer_chunks,
|
unsigned _buffer_chunks,
|
||||||
AudioFormat _configured_audio_format,
|
AudioFormat _configured_audio_format,
|
||||||
const ReplayGainConfig &_replay_gain_config) noexcept
|
const ReplayGainConfig &_replay_gain_config) noexcept
|
||||||
:listener(_listener), outputs(_outputs),
|
:listener(_listener), outputs(_outputs),
|
||||||
|
input_cache(_input_cache),
|
||||||
buffer_chunks(_buffer_chunks),
|
buffer_chunks(_buffer_chunks),
|
||||||
configured_audio_format(_configured_audio_format),
|
configured_audio_format(_configured_audio_format),
|
||||||
thread(BIND_THIS_METHOD(RunThread)),
|
thread(BIND_THIS_METHOD(RunThread)),
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
struct Tag;
|
struct Tag;
|
||||||
class PlayerListener;
|
class PlayerListener;
|
||||||
class PlayerOutputs;
|
class PlayerOutputs;
|
||||||
|
class InputCacheManager;
|
||||||
class DetachedSong;
|
class DetachedSong;
|
||||||
|
|
||||||
enum class PlayerState : uint8_t {
|
enum class PlayerState : uint8_t {
|
||||||
@ -116,6 +117,8 @@ class PlayerControl final : public AudioOutputClient {
|
|||||||
|
|
||||||
PlayerOutputs &outputs;
|
PlayerOutputs &outputs;
|
||||||
|
|
||||||
|
InputCacheManager *const input_cache;
|
||||||
|
|
||||||
const unsigned buffer_chunks;
|
const unsigned buffer_chunks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,6 +237,7 @@ class PlayerControl final : public AudioOutputClient {
|
|||||||
public:
|
public:
|
||||||
PlayerControl(PlayerListener &_listener,
|
PlayerControl(PlayerListener &_listener,
|
||||||
PlayerOutputs &_outputs,
|
PlayerOutputs &_outputs,
|
||||||
|
InputCacheManager *_input_cache,
|
||||||
unsigned buffer_chunks,
|
unsigned buffer_chunks,
|
||||||
AudioFormat _configured_audio_format,
|
AudioFormat _configured_audio_format,
|
||||||
const ReplayGainConfig &_replay_gain_config) noexcept;
|
const ReplayGainConfig &_replay_gain_config) noexcept;
|
||||||
|
@ -1132,6 +1132,7 @@ try {
|
|||||||
SetThreadName("player");
|
SetThreadName("player");
|
||||||
|
|
||||||
DecoderControl dc(mutex, cond,
|
DecoderControl dc(mutex, cond,
|
||||||
|
input_cache,
|
||||||
configured_audio_format,
|
configured_audio_format,
|
||||||
replay_gain_config);
|
replay_gain_config);
|
||||||
dc.StartThread();
|
dc.StartThread();
|
||||||
|
Loading…
Reference in New Issue
Block a user