1 /*****************************************************************************
2 * prefetch.c: prefetchinging module for VLC
3 *****************************************************************************
4 * Copyright © 2015 Rémi Denis-Courmont
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
29 #include <sys/types.h>
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_stream.h>
36 #include <vlc_interrupt.h>
42 vlc_cond_t wait_space
;
44 vlc_interrupt_t
*interrupt
;
57 uint64_t buffer_offset
;
58 uint64_t stream_offset
;
63 size_t seek_threshold
;
66 static ssize_t
ThreadRead(stream_t
*stream
, void *buf
, size_t length
)
68 stream_sys_t
*sys
= stream
->p_sys
;
69 int canc
= vlc_savecancel();
71 vlc_mutex_unlock(&sys
->lock
);
74 ssize_t val
= vlc_stream_ReadPartial(stream
->p_source
, buf
, length
);
76 vlc_mutex_lock(&sys
->lock
);
77 vlc_restorecancel(canc
);
81 static int ThreadSeek(stream_t
*stream
, uint64_t seek_offset
)
83 stream_sys_t
*sys
= stream
->p_sys
;
84 int canc
= vlc_savecancel();
86 vlc_mutex_unlock(&sys
->lock
);
88 int val
= vlc_stream_Seek(stream
->p_source
, seek_offset
);
89 if (val
!= VLC_SUCCESS
)
90 msg_Err(stream
, "cannot seek (to offset %"PRIu64
")", seek_offset
);
92 vlc_mutex_lock(&sys
->lock
);
93 vlc_restorecancel(canc
);
95 return (val
== VLC_SUCCESS
) ? 0 : -1;
98 static int ThreadControl(stream_t
*stream
, int query
, ...)
100 stream_sys_t
*sys
= stream
->p_sys
;
101 int canc
= vlc_savecancel();
103 vlc_mutex_unlock(&sys
->lock
);
109 ret
= vlc_stream_vaControl(stream
->p_source
, query
, ap
);
112 vlc_mutex_lock(&sys
->lock
);
113 vlc_restorecancel(canc
);
117 #define MAX_READ 65536
118 #define SEEK_THRESHOLD MAX_READ
120 static void *Thread(void *data
)
122 stream_t
*stream
= data
;
123 stream_sys_t
*sys
= stream
->p_sys
;
126 vlc_interrupt_set(sys
->interrupt
);
128 vlc_mutex_lock(&sys
->lock
);
129 mutex_cleanup_push(&sys
->lock
);
132 if (sys
->paused
!= paused
)
133 { /* Update pause state */
134 msg_Dbg(stream
, paused
? "resuming" : "pausing");
135 paused
= sys
->paused
;
136 ThreadControl(stream
, STREAM_SET_PAUSE_STATE
, paused
);
140 if (paused
|| sys
->error
)
141 { /* Wait for not paused and not failed */
142 vlc_cond_wait(&sys
->wait_space
, &sys
->lock
);
146 uint_fast64_t stream_offset
= sys
->stream_offset
;
148 if (stream_offset
< sys
->buffer_offset
)
149 { /* Need to seek backward */
150 if (ThreadSeek(stream
, stream_offset
) == 0)
152 sys
->buffer_offset
= stream_offset
;
153 sys
->buffer_length
= 0;
160 vlc_cond_signal(&sys
->wait_data
);
166 { /* Do not attempt to read at EOF - would busy loop */
167 vlc_cond_wait(&sys
->wait_space
, &sys
->lock
);
171 assert(stream_offset
>= sys
->buffer_offset
);
173 /* As long as there is space, the buffer will retain already read
174 * ("historical") data. The data can be used if/when seeking backward.
175 * Unread data is however given precedence if the buffer is full. */
176 uint64_t history
= stream_offset
- sys
->buffer_offset
;
178 /* If upstream supports seeking and if the downstream offset is far
179 * beyond the upstream offset, then attempt to skip forward.
180 * If it fails, assume upstream is well-behaved such that the failed
181 * seek is a no-op, and continue as if seeking was not supported.
182 * WARNING: Except problems with misbehaving access plug-ins. */
184 && history
>= (sys
->buffer_length
+ sys
->seek_threshold
))
186 if (ThreadSeek(stream
, stream_offset
) == 0)
188 sys
->buffer_offset
= stream_offset
;
189 sys
->buffer_length
= 0;
194 { /* Seek failure is not necessarily fatal here. We could read
195 * data instead until the desired seek offset. But in practice,
196 * not all upstream accesses handle reads after failed seek
197 * correctly. Furthermore, sys->stream_offset and/or
198 * sys->paused might have changed in the mean time. */
200 vlc_cond_signal(&sys
->wait_data
);
205 assert(sys
->buffer_size
>= sys
->buffer_length
);
207 size_t len
= sys
->buffer_size
- sys
->buffer_length
;
209 { /* Buffer is full */
211 { /* Wait for data to be read */
212 vlc_cond_wait(&sys
->wait_space
, &sys
->lock
);
216 /* Discard some historical data to make room. */
218 if (len
> sys
->read_size
)
219 len
= sys
->read_size
;
221 assert(len
<= sys
->buffer_length
);
222 sys
->buffer_offset
+= len
;
223 sys
->buffer_length
-= len
;
226 { /* Some streams cannot return a short data count and just wait for
227 * all requested data to become available (e.g. regular files). So
228 * we have to limit the data read in a single operation to avoid
229 * blocking for too long. */
230 if (len
> sys
->read_size
)
231 len
= sys
->read_size
;
234 size_t offset
= (sys
->buffer_offset
+ sys
->buffer_length
)
236 /* Do not step past the sharp edge of the circular buffer */
237 if (offset
+ len
> sys
->buffer_size
)
238 len
= sys
->buffer_size
- offset
;
240 ssize_t val
= ThreadRead(stream
, sys
->buffer
+ offset
, len
);
246 msg_Dbg(stream
, "end of stream");
250 assert((size_t)val
<= len
);
251 sys
->buffer_length
+= val
;
252 assert(sys
->buffer_length
<= sys
->buffer_size
);
253 //msg_Dbg(stream, "buffer: %zu/%zu", sys->buffer_length,
254 // sys->buffer_size);
255 vlc_cond_signal(&sys
->wait_data
);
257 vlc_assert_unreachable();
262 static int Seek(stream_t
*stream
, uint64_t offset
)
264 stream_sys_t
*sys
= stream
->p_sys
;
266 vlc_mutex_lock(&sys
->lock
);
267 sys
->stream_offset
= offset
;
269 vlc_cond_signal(&sys
->wait_space
);
270 vlc_mutex_unlock(&sys
->lock
);
274 static size_t BufferLevel(const stream_t
*stream
, bool *eof
)
276 stream_sys_t
*sys
= stream
->p_sys
;
280 if (sys
->stream_offset
< sys
->buffer_offset
)
282 if ((sys
->stream_offset
- sys
->buffer_offset
) >= sys
->buffer_length
)
287 return sys
->buffer_offset
+ sys
->buffer_length
- sys
->stream_offset
;
290 static ssize_t
Read(stream_t
*stream
, void *buf
, size_t buflen
)
292 stream_sys_t
*sys
= stream
->p_sys
;
299 vlc_mutex_lock(&sys
->lock
);
302 msg_Err(stream
, "reading while paused (buggy demux?)");
304 vlc_cond_signal(&sys
->wait_space
);
307 while ((copy
= BufferLevel(stream
, &eof
)) == 0 && !eof
)
313 vlc_mutex_unlock(&sys
->lock
);
317 vlc_interrupt_forward_start(sys
->interrupt
, data
);
318 vlc_cond_wait(&sys
->wait_data
, &sys
->lock
);
319 vlc_interrupt_forward_stop(data
);
322 offset
= sys
->stream_offset
% sys
->buffer_size
;
325 /* Do not step past the sharp edge of the circular buffer */
326 if (offset
+ copy
> sys
->buffer_size
)
327 copy
= sys
->buffer_size
- offset
;
329 memcpy(buf
, sys
->buffer
+ offset
, copy
);
330 sys
->stream_offset
+= copy
;
331 vlc_cond_signal(&sys
->wait_space
);
332 vlc_mutex_unlock(&sys
->lock
);
336 static int ReadDir(stream_t
*stream
, input_item_node_t
*node
)
338 (void) stream
; (void) node
;
342 static int Control(stream_t
*stream
, int query
, va_list args
)
344 stream_sys_t
*sys
= stream
->p_sys
;
348 case STREAM_CAN_SEEK
:
349 *va_arg(args
, bool *) = sys
->can_seek
;
351 case STREAM_CAN_FASTSEEK
:
352 *va_arg(args
, bool *) = false;
354 case STREAM_CAN_PAUSE
:
355 *va_arg(args
, bool *) = sys
->can_pause
;
357 case STREAM_CAN_CONTROL_PACE
:
358 *va_arg (args
, bool *) = sys
->can_pace
;
360 case STREAM_IS_DIRECTORY
:
362 case STREAM_GET_SIZE
:
363 if (sys
->size
== (uint64_t)-1)
365 *va_arg(args
, uint64_t *) = sys
->size
;
367 case STREAM_GET_PTS_DELAY
:
368 *va_arg(args
, int64_t *) = sys
->pts_delay
;
370 case STREAM_GET_TITLE_INFO
:
371 case STREAM_GET_TITLE
:
372 case STREAM_GET_SEEKPOINT
:
373 case STREAM_GET_META
:
375 case STREAM_GET_CONTENT_TYPE
:
376 if (sys
->content_type
== NULL
)
378 *va_arg(args
, char **) = strdup(sys
->content_type
);
380 case STREAM_GET_SIGNAL
:
382 case STREAM_SET_PAUSE_STATE
:
384 bool paused
= va_arg(args
, unsigned);
386 vlc_mutex_lock(&sys
->lock
);
387 sys
->paused
= paused
;
388 vlc_cond_signal(&sys
->wait_space
);
389 vlc_mutex_unlock (&sys
->lock
);
392 case STREAM_SET_TITLE
:
393 case STREAM_SET_SEEKPOINT
:
394 case STREAM_SET_PRIVATE_ID_STATE
:
395 case STREAM_SET_PRIVATE_ID_CA
:
396 case STREAM_GET_PRIVATE_ID_STATE
:
399 msg_Err(stream
, "unimplemented query (%d) in control", query
);
405 static int Open(vlc_object_t
*obj
)
407 stream_t
*stream
= (stream_t
*)obj
;
410 /* For local files, the operating system is likely to do a better work at
411 * caching/prefetching. Also, prefetching with this module could cause
412 * undesirable high load at start-up. Lastly, local files may require
413 * support for title/seekpoint and meta control requests. */
414 vlc_stream_Control(stream
->p_source
, STREAM_CAN_FASTSEEK
, &fast_seek
);
418 /* PID-filtered streams are not suitable for prefetching, as they would
419 * suffer excessive latency to enable a PID. DVB would also require support
420 * for the signal level and Conditional Access controls.
421 * TODO? For seekable streams, a forced could work around the problem. */
422 if (vlc_stream_Control(stream
->p_source
, STREAM_GET_PRIVATE_ID_STATE
, 0,
423 &(bool){ false }) == VLC_SUCCESS
)
426 stream_sys_t
*sys
= malloc(sizeof (*sys
));
427 if (unlikely(sys
== NULL
))
430 stream
->pf_read
= Read
;
431 stream
->pf_seek
= Seek
;
432 stream
->pf_control
= Control
;
434 vlc_stream_Control(stream
->p_source
, STREAM_CAN_SEEK
, &sys
->can_seek
);
435 vlc_stream_Control(stream
->p_source
, STREAM_CAN_PAUSE
, &sys
->can_pause
);
436 vlc_stream_Control(stream
->p_source
, STREAM_CAN_CONTROL_PACE
,
438 if (vlc_stream_Control(stream
->p_source
, STREAM_GET_SIZE
, &sys
->size
))
440 vlc_stream_Control(stream
->p_source
, STREAM_GET_PTS_DELAY
,
442 if (vlc_stream_Control(stream
->p_source
, STREAM_GET_CONTENT_TYPE
,
444 sys
->content_type
= NULL
;
449 sys
->buffer_offset
= 0;
450 sys
->stream_offset
= 0;
451 sys
->buffer_length
= 0;
452 sys
->buffer_size
= var_InheritInteger(obj
, "prefetch-buffer-size") << 10u;
453 sys
->read_size
= var_InheritInteger(obj
, "prefetch-read-size");
454 sys
->seek_threshold
= var_InheritInteger(obj
, "prefetch-seek-threshold");
456 uint64_t size
= stream_Size(stream
->p_source
);
458 { /* No point allocating a buffer larger than the source stream */
459 if (sys
->buffer_size
> size
)
460 sys
->buffer_size
= size
;
461 if (sys
->read_size
> size
)
462 sys
->read_size
= size
;
464 if (sys
->buffer_size
< sys
->read_size
)
465 sys
->buffer_size
= sys
->read_size
;
467 sys
->buffer
= malloc(sys
->buffer_size
);
468 if (sys
->buffer
== NULL
)
471 sys
->interrupt
= vlc_interrupt_create();
472 if (unlikely(sys
->interrupt
== NULL
))
475 vlc_mutex_init(&sys
->lock
);
476 vlc_cond_init(&sys
->wait_data
);
477 vlc_cond_init(&sys
->wait_space
);
481 if (vlc_clone(&sys
->thread
, Thread
, stream
, VLC_THREAD_PRIORITY_LOW
))
483 vlc_cond_destroy(&sys
->wait_space
);
484 vlc_cond_destroy(&sys
->wait_data
);
485 vlc_mutex_destroy(&sys
->lock
);
486 vlc_interrupt_destroy(sys
->interrupt
);
490 msg_Dbg(stream
, "using %zu bytes buffer, %zu bytes read",
491 sys
->buffer_size
, sys
->read_size
);
492 stream
->pf_read
= Read
;
493 stream
->pf_readdir
= ReadDir
;
494 stream
->pf_control
= Control
;
499 free(sys
->content_type
);
506 * Releases allocate resources.
508 static void Close (vlc_object_t
*obj
)
510 stream_t
*stream
= (stream_t
*)obj
;
511 stream_sys_t
*sys
= stream
->p_sys
;
513 vlc_cancel(sys
->thread
);
514 vlc_interrupt_kill(sys
->interrupt
);
515 vlc_join(sys
->thread
, NULL
);
516 vlc_interrupt_destroy(sys
->interrupt
);
517 vlc_cond_destroy(&sys
->wait_space
);
518 vlc_cond_destroy(&sys
->wait_data
);
519 vlc_mutex_destroy(&sys
->lock
);
522 free(sys
->content_type
);
527 set_category(CAT_INPUT
)
528 set_subcategory(SUBCAT_INPUT_STREAM_FILTER
)
529 set_capability("stream_filter", 0)
531 set_description(N_("Stream prefetch filter"))
532 set_callbacks(Open
, Close
)
534 add_integer("prefetch-buffer-size", 1 << 14, N_("Buffer size"),
535 N_("Prefetch buffer size (KiB)"), false)
536 change_integer_range(4, 1 << 20)
537 add_integer("prefetch-read-size", 1 << 14, N_("Read size"),
538 N_("Prefetch background read size (bytes)"), true)
539 change_integer_range(1, 1 << 29)
540 add_integer("prefetch-seek-threshold", 1 << 14, N_("Seek threshold"),
541 N_("Prefetch forward seek threshold (bytes)"), true)
542 change_integer_range(0, UINT64_C(1) << 60)