summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-06-16 22:07:48 +0200
committerwm4 <wm4@nowhere>2013-06-16 22:07:48 +0200
commitd81b71c7f7cb6f5b3a047fbfeeb41d4888e7c5b6 (patch)
tree0d50b238c1a798db9ef09838800c99653c06e408
parent4d3a2c7e0dac38546f5fc2c7737a6ec1f09e30f6 (diff)
parentf794444309a826478fefa00bf13d660793066b81 (diff)
downloadmpv-d81b71c7f7cb6f5b3a047fbfeeb41d4888e7c5b6.tar.bz2
mpv-d81b71c7f7cb6f5b3a047fbfeeb41d4888e7c5b6.tar.xz
Merge branch 'cache_new'
-rw-r--r--DOCS/man/en/options.rst21
-rw-r--r--Makefile2
-rwxr-xr-xconfigure15
-rw-r--r--core/mplayer.c39
-rw-r--r--core/options.c3
-rw-r--r--core/timeline/tl_matroska.c4
-rw-r--r--demux/aviheader.c3
-rw-r--r--demux/demux_asf.c1
-rw-r--r--demux/demux_avi.c1
-rw-r--r--demux/demux_lavf.c65
-rw-r--r--demux/demux_ts.c5
-rw-r--r--demux/ebml.c5
-rw-r--r--stream/cache.c590
-rw-r--r--stream/cache2.c735
-rw-r--r--stream/cache2.h27
-rw-r--r--stream/stream.c292
-rw-r--r--stream/stream.h89
-rw-r--r--stream/stream_cdda.c5
-rw-r--r--stream/stream_dvb.c1
-rw-r--r--stream/stream_dvd.c23
-rw-r--r--stream/stream_file.c5
-rw-r--r--stream/stream_ftp.c2
-rw-r--r--stream/stream_lavf.c1
-rw-r--r--stream/stream_radio.c13
-rw-r--r--stream/stream_smb.c1
-rw-r--r--stream/stream_vcd.c6
-rw-r--r--sub/subreader.c2
27 files changed, 907 insertions, 1049 deletions
diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst
index 34efa3c4bd..304eeba464 100644
--- a/DOCS/man/en/options.rst
+++ b/DOCS/man/en/options.rst
@@ -283,12 +283,15 @@
Adjust the brightness of the video signal (default: 0). Not supported by
all video output drivers.
---cache=<kBytes>
- Enable caching of the input stream (if not already enabled) and set the
- size of the cache in kilobytes. Caching is enabled by default (with a
- default cache size) for network streams. May be useful when playing files
- from slow media, but can also have negative effects, especially with file
- formats that require a lot of seeking, such as mp4. See also ``--no-cache``.
+--cache=<kBytes|no|auto>
+ Set the size of the cache in kilobytes, disable it with ``no``, or
+ automatically enable it if needed with ``auto`` (default: ``auto``).
+ With ``auto``, the cache will usually be enabled for network streams,
+ using a default size.
+
+ May be useful when playing files from slow media, but can also have
+ negative effects, especially with file formats that require a lot of
+ seeking, such as mp4.
Note that half the cache size will be used to allow fast seeking back. This
is also the reason why a full cache is usually reported as 50% full. The
@@ -315,6 +318,12 @@
filled to this position rather than performing a stream seek (default:
50).
+ This matters for small forward seeks. With slow streams (especially http
+ streams) there is a tradeoff between skipping the data between current
+ position and seek destination, or performing an actual seek. Depending
+ on the situation, either of these might be slower than the other method.
+ This option allows control over this.
+
--cdda=<option1:option2>
This option can be used to tune the CD Audio reading feature of mpv.
diff --git a/Makefile b/Makefile
index d39f37c971..9ac246dc36 100644
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,7 @@ SOURCES-$(PRIORITY) += osdep/priority.c
SOURCES-$(PVR) += stream/stream_pvr.c
SOURCES-$(RADIO) += stream/stream_radio.c
SOURCES-$(RADIO_CAPTURE) += stream/audio_in.c
-SOURCES-$(STREAM_CACHE) += stream/cache2.c
+SOURCES-$(STREAM_CACHE) += stream/cache.c
SOURCES-$(TV) += stream/stream_tv.c stream/tv.c \
stream/frequencies.c stream/tvi_dummy.c
diff --git a/configure b/configure
index 0b0913bf8c..d18d1cc547 100755
--- a/configure
+++ b/configure
@@ -493,9 +493,7 @@ libavdevice=auto
_stream_cache=yes
_priority=no
def_dos_paths="#define HAVE_DOS_PATHS 0"
-def_stream_cache="#define CONFIG_STREAM_CACHE 1"
def_priority="#undef CONFIG_PRIORITY"
-def_pthread_cache="#undef PTHREAD_CACHE"
need_shmem=yes
_build_man=auto
for ac_option do
@@ -1453,13 +1451,11 @@ else
fi
echores "$_pthreads"
-if cygwin ; then
- if test "$_pthreads" = yes ; then
- def_pthread_cache="#define PTHREAD_CACHE 1"
- else
- _stream_cache=no
- def_stream_cache="#undef CONFIG_STREAM_CACHE"
- fi
+_stream_cache="$_pthreads"
+if test "$_stream_cache" = yes ; then
+ def_stream_cache='#define CONFIG_STREAM_CACHE'
+else
+ def_stream_cache='#undef CONFIG_STREAM_CACHE'
fi
echocheck "rpath"
@@ -3239,7 +3235,6 @@ $def_priority
/* configurable options */
$def_stream_cache
-$def_pthread_cache
/* CPU stuff */
diff --git a/core/mplayer.c b/core/mplayer.c
index d38363b9ce..86260a0598 100644
--- a/core/mplayer.c
+++ b/core/mplayer.c
@@ -108,7 +108,6 @@
#ifdef CONFIG_DVBIN
#include "stream/dvbin.h"
#endif
-#include "stream/cache2.h"
//**************************************************************************//
// Playtree
@@ -137,10 +136,6 @@
#include "demux/demux.h"
#include "demux/stheader.h"
-#ifdef CONFIG_DVDREAD
-#include "stream/stream_dvd.h"
-#endif
-
#include "audio/filter/af.h"
#include "audio/decode/dec_audio.h"
#include "video/decode/dec_video.h"
@@ -938,13 +933,13 @@ static int find_new_tid(struct MPContext *mpctx, enum stream_type t)
// Map stream number (as used by libdvdread) to MPEG IDs (as used by demuxer).
static int map_id_from_demuxer(struct demuxer *d, enum stream_type type, int id)
{
- if (d->stream->type == STREAMTYPE_DVD && type == STREAM_SUB)
+ if (d->stream->uncached_type == STREAMTYPE_DVD && type == STREAM_SUB)
id = id & 0x1F;
return id;
}
static int map_id_to_demuxer(struct demuxer *d, enum stream_type type, int id)
{
- if (d->stream->type == STREAMTYPE_DVD && type == STREAM_SUB)
+ if (d->stream->uncached_type == STREAMTYPE_DVD && type == STREAM_SUB)
id = id | 0x20;
return id;
}
@@ -1013,9 +1008,9 @@ static void add_dvd_tracks(struct MPContext *mpctx)
#ifdef CONFIG_DVDREAD
struct demuxer *demuxer = mpctx->demuxer;
struct stream *stream = demuxer->stream;
- if (stream->type == STREAMTYPE_DVD) {
- int n_subs = dvd_number_of_subs(stream);
- for (int n = 0; n < n_subs; n++) {
+ struct stream_dvd_info_req info;
+ if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
+ for (int n = 0; n < info.num_subs; n++) {
struct track *track = talloc_ptrtype(NULL, track);
*track = (struct track) {
.type = STREAM_SUB,
@@ -1963,7 +1958,11 @@ static void set_dvdsub_fake_extradata(struct dec_sub *dec_sub, struct stream *st
int width, int height)
{
#ifdef CONFIG_DVDREAD
- if (st->type != STREAMTYPE_DVD)
+ if (!st)
+ return;
+
+ struct stream_dvd_info_req info;
+ if (stream_control(st, STREAM_CTRL_GET_DVD_INFO, &info) < 0)
return;
struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
@@ -1972,8 +1971,6 @@ static void set_dvdsub_fake_extradata(struct dec_sub *dec_sub, struct stream *st
float cmatrix[3][4];
mp_get_yuv2rgb_coeffs(&csp, cmatrix);
- int *palette = ((dvd_priv_t *)st->priv)->cur_pgc->palette;
-
if (width == 0 || height == 0) {
width = 720;
height = 480;
@@ -1983,7 +1980,7 @@ static void set_dvdsub_fake_extradata(struct dec_sub *dec_sub, struct stream *st
s = talloc_asprintf_append(s, "size: %dx%d\n", width, height);
s = talloc_asprintf_append(s, "palette: ");
for (int i = 0; i < 16; i++) {
- int color = palette[i];
+ int color = info.palette[i];
int c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
mp_map_int_color(cmatrix, 8, c);
color = (c[2] << 16) | (c[1] << 8) | c[0];
@@ -3942,7 +3939,7 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
struct stream *stream = open_stream(filename, &mpctx->opts, &format);
if (!stream)
goto err_out;
- stream_enable_cache_percent(stream, stream_cache,
+ stream_enable_cache_percent(&stream, stream_cache,
opts->stream_cache_min_percent,
opts->stream_cache_seek_min_percent);
// deal with broken demuxers: preselect streams
@@ -4225,10 +4222,7 @@ static void play_current_file(struct MPContext *mpctx)
}
// CACHE2: initial prefill: 20% later: 5% (should be set by -cacheopts)
-#ifdef CONFIG_DVBIN
-goto_enable_cache: ;
-#endif
- int res = stream_enable_cache_percent(mpctx->stream,
+ int res = stream_enable_cache_percent(&mpctx->stream,
opts->stream_cache_size,
opts->stream_cache_min_percent,
opts->stream_cache_seek_min_percent);
@@ -4238,6 +4232,10 @@ goto_enable_cache: ;
stream_set_capture_file(mpctx->stream, opts->stream_capture);
+#ifdef CONFIG_DVBIN
+goto_reopen_demuxer: ;
+#endif
+
//============ Open DEMUXERS --- DETECT file type =======================
mpctx->audio_delay = opts->audio_delay;
@@ -4424,9 +4422,8 @@ goto_enable_cache: ;
if (mpctx->dvbin_reopen) {
mpctx->stop_play = 0;
uninit_player(mpctx, INITIALIZED_ALL - (INITIALIZED_STREAM | INITIALIZED_GETCH2 | (opts->fixed_vo ? INITIALIZED_VO : 0)));
- cache_uninit(mpctx->stream);
mpctx->dvbin_reopen = 0;
- goto goto_enable_cache;
+ goto goto_reopen_demuxer;
}
#endif
diff --git a/core/options.c b/core/options.c
index d3dd9d19c8..f3e262fc17 100644
--- a/core/options.c
+++ b/core/options.c
@@ -322,7 +322,8 @@ const m_option_t mp_opts[] = {
#ifdef CONFIG_STREAM_CACHE
OPT_CHOICE_OR_INT("cache", stream_cache_size, 0, 32, 0x7fffffff,
- ({"no", -1}),
+ ({"no", 0},
+ {"auto", -1}),
OPTDEF_INT(-1)),
OPT_FLOATRANGE("cache-min", stream_cache_min_percent, 0, 0, 99),
OPT_FLOATRANGE("cache-seek-min", stream_cache_seek_min_percent, 0, 0, 99),
diff --git a/core/timeline/tl_matroska.c b/core/timeline/tl_matroska.c
index 98ba635bec..11fcc67583 100644
--- a/core/timeline/tl_matroska.c
+++ b/core/timeline/tl_matroska.c
@@ -141,7 +141,7 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream,
return -1;
}
- stream_enable_cache_percent(*stream,
+ stream_enable_cache_percent(stream,
opts->stream_cache_size,
opts->stream_cache_min_percent,
opts->stream_cache_seek_min_percent);
@@ -230,7 +230,7 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx,
char *main_filename = mpctx->demuxer->filename;
mp_msg(MSGT_CPLAYER, MSGL_INFO, "This file references data from "
"other sources.\n");
- if (mpctx->demuxer->stream->type != STREAMTYPE_FILE) {
+ if (mpctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
mp_msg(MSGT_CPLAYER, MSGL_WARN, "Playback source is not a "
"normal disk file. Will not search for related files.\n");
} else {
diff --git a/demux/aviheader.c b/demux/aviheader.c
index 2226be25d4..c1b9c59692 100644
--- a/demux/aviheader.c
+++ b/demux/aviheader.c
@@ -479,7 +479,6 @@ if (priv->isodml && (index_mode==-1 || index_mode==0 || index_mode==1)) {
// read the standard indices
for (cx = &priv->suidx[0], i=0; i<priv->suidx_size; cx++, i++) {
- stream_reset(demuxer->stream);
for (j=0; j<cx->nEntriesInUse; j++) {
int ret1, ret2;
memset(&cx->stdidx[j], 0, 32);
@@ -543,7 +542,6 @@ if (priv->isodml && (index_mode==-1 || index_mode==0 || index_mode==1)) {
{
uint32_t id;
uint32_t db = 0;
- stream_reset (demuxer->stream);
// find out the video stream id. I have seen files with 01db.
for (idx = &((AVIINDEXENTRY *)priv->idx)[0], i=0; i<priv->idx_size; i++, idx++){
@@ -590,7 +588,6 @@ freeout:
if(index_mode>=2 || (priv->idx_size==0 && index_mode==1)){
int idx_pos = 0;
// build index for file:
- stream_reset(demuxer->stream);
stream_seek(demuxer->stream,demuxer->movi_start);
priv->idx_size=0;
diff --git a/demux/demux_asf.c b/demux/demux_asf.c
index 0b8da6930f..f800e09dc3 100644
--- a/demux/demux_asf.c
+++ b/demux/demux_asf.c
@@ -628,7 +628,6 @@ static demuxer_t* demux_open_asf(demuxer_t* demuxer)
init_priv(asf);
if (!read_asf_header(demuxer,asf))
return NULL;
- stream_reset(demuxer->stream);
stream_seek(demuxer->stream,demuxer->movi_start);
// demuxer->idx_pos=0;
// demuxer->endpos=avi_header.movi_end;
diff --git a/demux/demux_avi.c b/demux/demux_avi.c
index daf542bfac..a07f022cde 100644
--- a/demux/demux_avi.c
+++ b/demux/demux_avi.c
@@ -456,7 +456,6 @@ static demuxer_t* demux_open_avi(demuxer_t* demuxer){
demuxer->video->id=-1; // autodetect
}
- stream_reset(demuxer->stream);
stream_seek(demuxer->stream,demuxer->movi_start);
if(priv->idx_size>1){
// decide index format:
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index d3b64d3009..d8e8109c93 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -47,7 +47,7 @@
#include "core/m_option.h"
#define INITIAL_PROBE_SIZE STREAM_BUFFER_SIZE
-#define PROBE_BUF_SIZE (2 * 1024 * 1024)
+#define PROBE_BUF_SIZE FFMIN(STREAM_MAX_BUFFER_SIZE, 2 * 1024 * 1024)
#define OPT_BASE_STRUCT struct MPOpts
@@ -143,7 +143,6 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
return -1;
current_pos = stream_tell(stream);
if (stream_seek(stream, pos) == 0) {
- stream_reset(stream);
stream_seek(stream, current_pos);
return -1;
}
@@ -190,18 +189,15 @@ static int lavf_check_file(demuxer_t *demuxer)
{
struct MPOpts *opts = demuxer->opts;
struct lavfdopts *lavfdopts = &opts->lavfdopts;
- AVProbeData avpd;
+ struct stream *s = demuxer->stream;
lavf_priv_t *priv;
- int probe_data_size = 0;
- int read_size = INITIAL_PROBE_SIZE;
- int score;
assert(!demuxer->priv);
demuxer->priv = talloc_zero(NULL, lavf_priv_t);
priv = demuxer->priv;
priv->autoselect_sub = -1;
- priv->filename = demuxer->stream->url;
+ priv->filename = s->url;
if (!priv->filename) {
priv->filename = "mp:unknown";
mp_msg(MSGT_DEMUX, MSGL_WARN, "Stream url is not set!\n");
@@ -210,7 +206,7 @@ static int lavf_check_file(demuxer_t *demuxer)
priv->filename = remove_prefix(priv->filename, prefixes);
char *avdevice_format = NULL;
- if (demuxer->stream->type == STREAMTYPE_AVDEVICE) {
+ if (s->type == STREAMTYPE_AVDEVICE) {
// always require filename in the form "format:filename"
char *sep = strchr(priv->filename, ':');
if (!sep) {
@@ -237,7 +233,7 @@ static int lavf_check_file(demuxer_t *demuxer)
const char *format = lavfdopts->format;
if (!format)
- format = demuxer->stream->lavf_type;
+ format = s->lavf_type;
if (!format)
format = avdevice_format;
if (format) {
@@ -262,38 +258,43 @@ static int lavf_check_file(demuxer_t *demuxer)
if (lavfdopts->probescore)
min_probe = lavfdopts->probescore;
- avpd.buf = av_mallocz(FFMAX(BIO_BUFFER_SIZE, PROBE_BUF_SIZE) +
- FF_INPUT_BUFFER_PADDING_SIZE);
- do {
- read_size = stream_read(demuxer->stream, avpd.buf + probe_data_size,
- read_size);
- if (read_size < 0) {
- av_free(avpd.buf);
- return 0;
- }
- probe_data_size += read_size;
- avpd.filename = priv->filename;
- avpd.buf_size = probe_data_size;
+ AVProbeData avpd = {
+ .filename = priv->filename,
+ .buf_size = 0,
+ .buf = av_mallocz(PROBE_BUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE),
+ };
+
+ while (avpd.buf_size < PROBE_BUF_SIZE) {
+ int nsize = av_clip(avpd.buf_size * 2, INITIAL_PROBE_SIZE,
+ PROBE_BUF_SIZE);
+ int read_size = stream_read(s, avpd.buf + avpd.buf_size,
+ nsize - avpd.buf_size);
+ if (read_size <= 0)
+ break;
- score = 0;
- priv->avif = av_probe_input_format2(&avpd, probe_data_size > 0, &score);
+ avpd.buf_size += read_size;
+
+ int score = 0;
+ priv->avif = av_probe_input_format2(&avpd, avpd.buf_size > 0, &score);
if (priv->avif) {
mp_msg(MSGT_HEADER, MSGL_V, "Found '%s' at score=%d size=%d.\n",
- priv->avif->name, score, probe_data_size);
- }
+ priv->avif->name, score, avpd.buf_size);
- if (priv->avif && score >= min_probe)
- break;
- if (priv->avif && expected_format) {
- if (strcmp(priv->avif->name, expected_format) == 0 &&
- score >= expected_format_probescore)
+ if (score >= min_probe)
break;
+
+ if (expected_format) {
+ if (strcmp(priv->avif->name, expected_format) == 0 &&
+ score >= expected_format_probescore)
+ break;
+ }
}
priv->avif = NULL;
- read_size = FFMIN(2 * read_size, PROBE_BUF_SIZE - probe_data_size);
- } while (read_size > 0 && probe_data_size < PROBE_BUF_SIZE);
+ }
+
+ stream_unread_buffer(s, avpd.buf, avpd.buf_size);
av_free(avpd.buf);
if (!priv->avif) {
diff --git a/demux/demux_ts.c b/demux/demux_ts.c
index 8b2a2d84ba..a16891907d 100644
--- a/demux/demux_ts.c
+++ b/demux/demux_ts.c
@@ -470,7 +470,6 @@ static int ts_check_file(demuxer_t * demuxer)
if(_read < buf_size-1)
{
mp_msg(MSGT_DEMUX, MSGL_V, "COULDN'T READ ENOUGH DATA, EXITING TS_CHECK\n");
- stream_reset(demuxer->stream);
return 0;
}
@@ -992,9 +991,6 @@ static demuxer_t *demux_open_ts(demuxer_t * demuxer)
demuxer->type= DEMUXER_TYPE_MPEG_TS;
demuxer->ts_resets_possible = true;
-
- stream_reset(demuxer->stream);
-
packet_size = ts_check_file(demuxer);
if(!packet_size)
return NULL;
@@ -1079,7 +1075,6 @@ static demuxer_t *demux_open_ts(demuxer_t * demuxer)
start_pos - priv->ts.packet_size;
demuxer->movi_start = start_pos;
demuxer->reference_clock = MP_NOPTS_VALUE;
- stream_reset(demuxer->stream);
stream_seek(demuxer->stream, start_pos); //IF IT'S FROM A PIPE IT WILL FAIL, BUT WHO CARES?
diff --git a/demux/ebml.c b/demux/ebml.c
index 52332cd0c5..98ab1ef306 100644
--- a/demux/ebml.c
+++ b/demux/ebml.c
@@ -308,11 +308,10 @@ int ebml_read_skip_or_resync_cluster(stream_t *s, uint64_t *length)
*length = len + l;
int64_t pos = stream_tell(s);
- stream_skip(s, len);
// When reading corrupted elements, len will often be a random high number,
- // and stream_skip() will set EOF.
- if (s->eof) {
+ // and stream_skip() will fail when skipping past EOF.
+ if (!stream_skip(s, len)) {
stream_seek(s, pos);
goto resync;
}
diff --git a/stream/cache.c b/stream/cache.c
new file mode 100644
index 0000000000..a3134b7c4a
--- /dev/null
+++ b/stream/cache.c
@@ -0,0 +1,590 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+// Time in seconds the main thread waits for the cache thread. On wakeups, the
+// code checks for user requested aborts and also prints warnings that the
+// cache is being slow.
+#define CACHE_WAIT_TIME 0.5
+
+// The time the cache sleeps in idle mode. This controls how often the cache
+// retries reading from the stream after EOF has reached (in case the stream is
+// actually readable again, for example if data has been appended to a file).
+// Note that if this timeout is too low, the player will waste too much CPU
+// when player is paused.
+#define CACHE_IDLE_SLEEP_TIME 1.0
+
+// Time in seconds the cache updates "cached" controls. Note that idle mode
+// will block the cache from doing this, and this timeout is honored only if
+// the cache is active.
+#define CACHE_UPDATE_CONTROLS_TIME 0.1
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include <libavutil/common.h>
+
+#include "config.h"
+
+#include "osdep/timer.h"
+
+#include "core/mp_msg.h"
+
+#include "stream.h"
+#include "core/mp_common.h"
+
+
+// Note: (struct priv*)(cache->priv)->cache == cache
+struct priv {
+ pthread_t cache_thread;
+ bool cache_thread_running;
+ pthread_mutex_t mutex;
+ pthread_cond_t wakeup;
+
+ // Constants (as long as cache thread is running)
+ unsigned char *buffer; // base pointer of the allocated buffer memory
+ int64_t buffer_size; // size of the allocated buffer memory
+ int64_t back_size; // keep back_size amount of old bytes for backward seek
+ int64_t fill_limit; // we should fill buffer only if space>=fill_limit
+ int64_t seek_limit; // keep filling cache if distance is less that seek limit
+ struct byte_meta *bm; // additional per-byte metadata
+
+ // Owned by the main thread
+ stream_t *cache; // wrapper stream, used by demuxer etc.
+
+ // Owned by the cache thread
+ stream_t *stream; // "real" stream, used to read from the source media
+
+ // All the following members are shared between the threads.
+ // You must lock the mutex to access them.
+
+ // Ringbuffer
+ int64_t min_filepos; // range of file that is cached in the buffer
+ int64_t max_filepos; // ... max_filepos being the last read position
+ bool eof; // true if max_filepos = EOF
+ int64_t offset; // buffer[offset] correponds to max_filepos
+
+ bool idle; // cache thread has stopped reading
+
+ int64_t read_filepos; // client read position (mirrors cache->pos)
+ int control; // requested STREAM_CTRL_... or CACHE_CTRL_...
+ void *control_arg; // temporary for executing STREAM_CTRLs
+ int control_res;
+ bool control_flush;
+
+ // Cached STREAM_CTRLs
+ double stream_time_length;
+ double stream_start_time;
+ int64_t stream_size;
+ bool stream_manages_timeline;
+ int stream_cache_idle;
+ int stream_cache_fill;
+};
+
+// Store additional per-byte metadata. Since per-byte would be way too
+// inefficient, store it only for every BYTE_META_CHUNK_SIZE byte.
+struct byte_meta {
+ float stream_pts;
+};
+
+enum {
+ BYTE_META_CHUNK_SIZE = 16 * 1024,
+
+ CACHE_INTERRUPTED = -1,
+
+ CACHE_CTRL_NONE = 0,
+ CACHE_CTRL_QUIT = -1,
+ CACHE_CTRL_PING = -2,
+};
+
+// pthread_cond_timedwait() with a relative timeout in seconds
+static int cond_timed_wait(pthread_cond_t *cond, pthread_mutex_t *mutex,
+ double timeout)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ unsigned long seconds = (int)timeout;
+ unsigned long nsecs = (timeout - seconds) * 1000000000UL;
+ if (nsecs + ts.tv_nsec >= 1000000000UL) {
+ seconds += 1;
+ nsecs -= 1000000000UL;
+ }
+ ts.tv_sec += seconds;
+ ts.tv_nsec += nsecs;
+ return pthread_cond_timedwait(cond, mutex, &ts);
+}
+
+// Used by the main thread to wakeup the cache thread, and to wait for the
+// cache thread. The cache mutex has to be locked when calling this function.
+// *retries should be set to 0 on the first call.
+// Returns CACHE_INTERRUPTED if the caller is supposed to abort.
+static int cache_wakeup_and_wait(struct priv *s, int *retries)
+{
+ if (stream_check_interrupt(0))
+ return CACHE_INTERRUPTED;
+
+ // Print a "more severe" warning after waiting 1 second and no new data
+ // (time calculation assumes the number of spurious wakeups is very low)
+ if ((*retries) * CACHE_WAIT_TIME >= 1.0) {
+ mp_msg(MSGT_CACHE, MSGL_ERR, "Cache keeps not responding.\n");
+ } else if (*retries > 0) {
+ mp_msg(MSGT_CACHE, MSGL_WARN,
+ "Cache is not responding - slow/stuck network connection?\n");
+ }
+ (*retries) += 1;
+
+ pthread_cond_signal(&s->wakeup);
+ cond_timed_wait(&s->wakeup, &s->mutex, CACHE_WAIT_TIME);
+
+ return 0;
+}
+
+// Runs in the cache thread
+static void cache_drop_contents(struct priv *s)
+{
+ s->offset = s->min_filepos = s->max_filepos = s->read_filepos;
+ s->eof = 0;
+}
+
+// Runs in the main thread
+// mutex must be held, but is sometimes temporarily dropped
+static int cache_read(struct priv *s, unsigned char *buf, int size)
+{
+ if (size <= 0)
+ return 0;
+
+ int retry = 0;
+ while (s->read_filepos >= s->max_filepos ||
+ s->read_filepos < s->min_filepos)
+ {
+ if (s->eof && s->read_filepos >= s->max_filepos)
+ return 0;
+ if (cache_wakeup_and_wait(s, &retry) == CACHE_INTERRUPTED)
+ return 0;
+ }
+
+ int64_t newb = s->max_filepos - s->read_filepos; // new bytes in the buffer
+
+ int64_t pos = s->read_filepos - s->offset; // file pos to buffer memory pos
+ if (pos < 0)
+ pos += s->buffer_size;
+ else if (pos >= s->buffer_size)
+ pos -= s->buffer_size;
+
+ if (newb > s->buffer_size - pos)
+ newb = s->buffer_size - pos; // handle wrap...
+
+ newb = FFMIN(newb, size);
+
+ memcpy(buf, &s->buffer[pos], newb);
+
+ s->read_filepos += newb;
+ return newb;
+}
+
+// Runs in the cache thread.
+// Returns true if reading was attempted, and the mutex was shortly unlocked.
+static bool cache_fill(struct priv *s)
+{
+ int64_t read = s->read_filepos;
+ int len;
+
+ if (read < s->min_filepos || read > s->max_filepos) {
+ // seek...
+ mp_msg(MSGT_CACHE, MSGL_DBG2,
+ "Out of boundaries... seeking to 0x%" PRIX64 " \n", read);
+ // drop cache contents only if seeking backward or too much fwd.
+ // This is also done for on-disk files, since it loses the backseek cache.
+ // That in turn can cause major bandwidth increase and performance
+ // issues with e.g. mov or badly interleaved files
+ if (read < s->min_filepos || read >= s->max_filepos + s->seek_limit) {
+ mp_msg(MSGT_CACHE, MSGL_V, "Dropping cache at pos %"PRId64", "
+ "cached range: %"PRId64"-%"PRId64".\n", read,
+ s->min_filepos, s->max_filepos);
+ cache_drop_contents(s);
+ stream_seek(s->stream, read);
+ }
+ }
+
+ // number of buffer bytes which should be preserved in backwards direction
+ int64_t back = av_clip64(read - s->min_filepos, 0, s->back_size);
+
+ // number of buffer bytes that are valid and can be read
+ int64_t newb = FFMAX(s->max_filepos - read, 0);
+
+ // max. number of bytes that can be written (starting from max_filepos)
+ int64_t space = s->buffer_size - (newb + back);
+
+ // offset into the buffer that maps to max_filepos
+ int pos = s->max_filepos - s->offset;
+ if (pos >= s->buffer_size)
+ pos -= s->buffer_size; // wrap-around
+
+ if (space < s->fill_limit) {
+ s->idle = true;
+ return false;
+ }
+
+ // limit to end of buffer (without wrapping)
+ if (pos + space >= s->buffer_size)
+ space = s->buffer_size - pos;
+
+ // limit read size (or else would block and read the entire buffer in 1 call)
+ space = FFMIN(space, s->stream->read_chunk);
+
+ // back+newb+space <= buffer_size
+ int64_t back2 = s->buffer_size - (space + newb); // max back size
+ if (s->min_filepos < (read - back2))
+ s->min_filepos = read - back2;
+
+ // The read call might take a long time and block, so drop the lock.
+ pthread_mutex_unlock(&s->mutex);
+ len = stream_read_partial(s->stream, &s->buffer[po