diff options
-rw-r--r-- | DOCS/man/en/options.rst | 21 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rwxr-xr-x | configure | 15 | ||||
-rw-r--r-- | core/mplayer.c | 39 | ||||
-rw-r--r-- | core/options.c | 3 | ||||
-rw-r--r-- | core/timeline/tl_matroska.c | 4 | ||||
-rw-r--r-- | demux/aviheader.c | 3 | ||||
-rw-r--r-- | demux/demux_asf.c | 1 | ||||
-rw-r--r-- | demux/demux_avi.c | 1 | ||||
-rw-r--r-- | demux/demux_lavf.c | 65 | ||||
-rw-r--r-- | demux/demux_ts.c | 5 | ||||
-rw-r--r-- | demux/ebml.c | 5 | ||||
-rw-r--r-- | stream/cache.c | 590 | ||||
-rw-r--r-- | stream/cache2.c | 735 | ||||
-rw-r--r-- | stream/cache2.h | 27 | ||||
-rw-r--r-- | stream/stream.c | 292 | ||||
-rw-r--r-- | stream/stream.h | 89 | ||||
-rw-r--r-- | stream/stream_cdda.c | 5 | ||||
-rw-r--r-- | stream/stream_dvb.c | 1 | ||||
-rw-r--r-- | stream/stream_dvd.c | 23 | ||||
-rw-r--r-- | stream/stream_file.c | 5 | ||||
-rw-r--r-- | stream/stream_ftp.c | 2 | ||||
-rw-r--r-- | stream/stream_lavf.c | 1 | ||||
-rw-r--r-- | stream/stream_radio.c | 13 | ||||
-rw-r--r-- | stream/stream_smb.c | 1 | ||||
-rw-r--r-- | stream/stream_vcd.c | 6 | ||||
-rw-r--r-- | sub/subreader.c | 2 |
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. @@ -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 @@ -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[pos], space); + pthread_mutex_lock(&s->mutex); + + int m1 = pos / BYTE_META_CHUNK_SIZE; + int m2 = (s->buffer_size + pos - 1) % s->buffer_size / BYTE_META_CHUNK_SIZE; + if (m1 != m2) { + double pts; + if (stream_control(s->stream, STREAM_CTRL_GET_CURRENT_TIME, &pts) <= 0) + pts = MP_NOPTS_VALUE; + s->bm[m1] = (struct byte_meta) { + .stream_pts = pts, + }; + } + + s->max_filepos += len; + if (pos + len == s->buffer_size) + s->offset += s->buffer_size; // wrap... + + s->eof = len > 0 ? 0 : 1; + s->idle = s->eof; + + pthread_cond_signal(&s->wakeup); + + return true; +} + +static void update_cached_controls(struct priv *s) +{ + double d; + s->stream_time_length = 0; + if (stream_control(s->stream, STREAM_CTRL_GET_TIME_LENGTH, &d) == STREAM_OK) + s->stream_time_lengt |