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>
40 struct stream_ctrl
*next
;
56 vlc_cond_t wait_space
;
58 vlc_interrupt_t
*interrupt
;
71 uint64_t buffer_offset
;
72 uint64_t stream_offset
;
76 size_t seek_threshold
;
78 struct stream_ctrl
*controls
;
81 static ssize_t
ThreadRead(stream_t
*stream
, void *buf
, size_t length
)
83 stream_sys_t
*sys
= stream
->p_sys
;
84 int canc
= vlc_savecancel();
86 vlc_mutex_unlock(&sys
->lock
);
89 ssize_t val
= vlc_stream_ReadPartial(stream
->s
, buf
, length
);
91 vlc_mutex_lock(&sys
->lock
);
92 vlc_restorecancel(canc
);
96 static int ThreadSeek(stream_t
*stream
, uint64_t seek_offset
)
98 stream_sys_t
*sys
= stream
->p_sys
;
99 int canc
= vlc_savecancel();
101 vlc_mutex_unlock(&sys
->lock
);
103 int val
= vlc_stream_Seek(stream
->s
, seek_offset
);
104 if (val
!= VLC_SUCCESS
)
105 msg_Err(stream
, "cannot seek (to offset %"PRIu64
")", seek_offset
);
107 vlc_mutex_lock(&sys
->lock
);
108 vlc_restorecancel(canc
);
110 return (val
== VLC_SUCCESS
) ? 0 : -1;
113 static int ThreadControl(stream_t
*stream
, int query
, ...)
115 stream_sys_t
*sys
= stream
->p_sys
;
116 int canc
= vlc_savecancel();
118 vlc_mutex_unlock(&sys
->lock
);
124 ret
= vlc_stream_vaControl(stream
->s
, query
, ap
);
127 vlc_mutex_lock(&sys
->lock
);
128 vlc_restorecancel(canc
);
132 static void *Thread(void *data
)
134 stream_t
*stream
= data
;
135 stream_sys_t
*sys
= stream
->p_sys
;
138 vlc_interrupt_set(sys
->interrupt
);
140 vlc_mutex_lock(&sys
->lock
);
141 mutex_cleanup_push(&sys
->lock
);
144 struct stream_ctrl
*ctrl
= sys
->controls
;
146 if (unlikely(ctrl
!= NULL
))
148 sys
->controls
= ctrl
->next
;
149 ThreadControl(stream
, ctrl
->query
, ctrl
->id_state
.id
,
150 ctrl
->id_state
.state
);
155 if (sys
->paused
!= paused
)
156 { /* Update pause state */
157 msg_Dbg(stream
, paused
? "resuming" : "pausing");
158 paused
= sys
->paused
;
159 ThreadControl(stream
, STREAM_SET_PAUSE_STATE
, paused
);
163 if (paused
|| sys
->error
)
164 { /* Wait for not paused and not failed */
165 vlc_cond_wait(&sys
->wait_space
, &sys
->lock
);
169 uint_fast64_t stream_offset
= sys
->stream_offset
;
171 if (stream_offset
< sys
->buffer_offset
)
172 { /* Need to seek backward */
173 if (ThreadSeek(stream
, stream_offset
) == 0)
175 sys
->buffer_offset
= stream_offset
;
176 sys
->buffer_length
= 0;
183 vlc_cond_signal(&sys
->wait_data
);
189 { /* Do not attempt to read at EOF - would busy loop */
190 vlc_cond_wait(&sys
->wait_space
, &sys
->lock
);
194 assert(stream_offset
>= sys
->buffer_offset
);
196 /* As long as there is space, the buffer will retain already read
197 * ("historical") data. The data can be used if/when seeking backward.
198 * Unread data is however given precedence if the buffer is full. */
199 uint64_t history
= stream_offset
- sys
->buffer_offset
;
201 /* If upstream supports seeking and if the downstream offset is far
202 * beyond the upstream offset, then attempt to skip forward.
203 * If it fails, assume upstream is well-behaved such that the failed
204 * seek is a no-op, and continue as if seeking was not supported.
205 * WARNING: Except problems with misbehaving access plug-ins. */
207 && history
>= (sys
->buffer_length
+ sys
->seek_threshold
))
209 if (ThreadSeek(stream
, stream_offset
) == 0)
211 sys
->buffer_offset
= stream_offset
;
212 sys
->buffer_length
= 0;
217 { /* Seek failure is not necessarily fatal here. We could read
218 * data instead until the desired seek offset. But in practice,
219 * not all upstream accesses handle reads after failed seek
220 * correctly. Furthermore, sys->stream_offset and/or
221 * sys->paused might have changed in the mean time. */
223 vlc_cond_signal(&sys
->wait_data
);
228 assert(sys
->buffer_size
>= sys
->buffer_length
);
230 size_t len
= sys
->buffer_size
- sys
->buffer_length
;
232 { /* Buffer is full */
234 { /* Wait for data to be read */
235 vlc_cond_wait(&sys
->wait_space
, &sys
->lock
);
239 /* Discard some historical data to make room. */
242 assert(len
<= sys
->buffer_length
);
243 sys
->buffer_offset
+= len
;
244 sys
->buffer_length
-= len
;
247 size_t offset
= (sys
->buffer_offset
+ sys
->buffer_length
)
249 /* Do not step past the sharp edge of the circular buffer */
250 if (offset
+ len
> sys
->buffer_size
)
251 len
= sys
->buffer_size
- offset
;
253 ssize_t val
= ThreadRead(stream
, sys
->buffer
+ offset
, len
);
259 msg_Dbg(stream
, "end of stream");
263 assert((size_t)val
<= len
);
264 sys
->buffer_length
+= val
;
265 assert(sys
->buffer_length
<= sys
->buffer_size
);
266 //msg_Dbg(stream, "buffer: %zu/%zu", sys->buffer_length,
267 // sys->buffer_size);
268 vlc_cond_signal(&sys
->wait_data
);
270 vlc_assert_unreachable();
275 static int Seek(stream_t
*stream
, uint64_t offset
)
277 stream_sys_t
*sys
= stream
->p_sys
;
279 vlc_mutex_lock(&sys
->lock
);
280 sys
->stream_offset
= offset
;
282 vlc_cond_signal(&sys
->wait_space
);
283 vlc_mutex_unlock(&sys
->lock
);
287 static size_t BufferLevel(const stream_t
*stream
, bool *eof
)
289 stream_sys_t
*sys
= stream
->p_sys
;
293 if (sys
->stream_offset
< sys
->buffer_offset
)
295 if ((sys
->stream_offset
- sys
->buffer_offset
) >= sys
->buffer_length
)
300 return sys
->buffer_offset
+ sys
->buffer_length
- sys
->stream_offset
;
303 static ssize_t
Read(stream_t
*stream
, void *buf
, size_t buflen
)
305 stream_sys_t
*sys
= stream
->p_sys
;
312 vlc_mutex_lock(&sys
->lock
);
315 msg_Err(stream
, "reading while paused (buggy demux?)");
317 vlc_cond_signal(&sys
->wait_space
);
320 while ((copy
= BufferLevel(stream
, &eof
)) == 0 && !eof
)
326 vlc_mutex_unlock(&sys
->lock
);
330 vlc_interrupt_forward_start(sys
->interrupt
, data
);
331 vlc_cond_wait(&sys
->wait_data
, &sys
->lock
);
332 vlc_interrupt_forward_stop(data
);
335 offset
= sys
->stream_offset
% sys
->buffer_size
;
338 /* Do not step past the sharp edge of the circular buffer */
339 if (offset
+ copy
> sys
->buffer_size
)
340 copy
= sys
->buffer_size
- offset
;
342 memcpy(buf
, sys
->buffer
+ offset
, copy
);
343 sys
->stream_offset
+= copy
;
344 vlc_cond_signal(&sys
->wait_space
);
345 vlc_mutex_unlock(&sys
->lock
);
349 static int Control(stream_t
*stream
, int query
, va_list args
)
351 stream_sys_t
*sys
= stream
->p_sys
;
355 case STREAM_CAN_SEEK
:
356 *va_arg(args
, bool *) = sys
->can_seek
;
358 case STREAM_CAN_FASTSEEK
:
359 *va_arg(args
, bool *) = false;
361 case STREAM_CAN_PAUSE
:
362 *va_arg(args
, bool *) = sys
->can_pause
;
364 case STREAM_CAN_CONTROL_PACE
:
365 *va_arg (args
, bool *) = sys
->can_pace
;
367 case STREAM_GET_SIZE
:
368 if (sys
->size
== (uint64_t)-1)
370 *va_arg(args
, uint64_t *) = sys
->size
;
372 case STREAM_GET_PTS_DELAY
:
373 *va_arg(args
, vlc_tick_t
*) = sys
->pts_delay
;
375 case STREAM_GET_TITLE_INFO
:
376 case STREAM_GET_TITLE
:
377 case STREAM_GET_SEEKPOINT
:
378 case STREAM_GET_META
:
380 case STREAM_GET_CONTENT_TYPE
:
381 if (sys
->content_type
== NULL
)
383 *va_arg(args
, char **) = strdup(sys
->content_type
);
385 case STREAM_GET_SIGNAL
:
386 case STREAM_GET_TAGS
:
388 case STREAM_SET_PAUSE_STATE
:
390 bool paused
= va_arg(args
, unsigned);
392 vlc_mutex_lock(&sys
->lock
);
393 sys
->paused
= paused
;
394 vlc_cond_signal(&sys
->wait_space
);
395 vlc_mutex_unlock (&sys
->lock
);
398 case STREAM_SET_TITLE
:
399 case STREAM_SET_SEEKPOINT
:
401 case STREAM_SET_PRIVATE_ID_STATE
:
403 struct stream_ctrl
*ctrl
= malloc(sizeof (*ctrl
)), **pp
;
404 if (unlikely(ctrl
== NULL
))
409 ctrl
->id_state
.id
= va_arg(args
, int);
410 ctrl
->id_state
.state
= va_arg(args
, int);
411 vlc_mutex_lock(&sys
->lock
);
412 for (pp
= &sys
->controls
; *pp
!= NULL
; pp
= &((*pp
)->next
));
414 vlc_cond_signal(&sys
->wait_space
);
415 vlc_mutex_unlock(&sys
->lock
);
418 case STREAM_SET_PRIVATE_ID_CA
:
419 case STREAM_GET_PRIVATE_ID_STATE
:
422 msg_Err(stream
, "unimplemented query (%d) in control", query
);
428 static int Open(vlc_object_t
*obj
)
430 stream_t
*stream
= (stream_t
*)obj
;
433 /* For local files, the operating system is likely to do a better work at
434 * caching/prefetching. Also, prefetching with this module could cause
435 * undesirable high load at start-up. Lastly, local files may require
436 * support for title/seekpoint and meta control requests. */
437 vlc_stream_Control(stream
->s
, STREAM_CAN_FASTSEEK
, &fast_seek
);
441 /* PID-filtered streams are not suitable for prefetching, as they would
442 * suffer excessive latency to enable a PID. DVB would also require support
443 * for the signal level and Conditional Access controls.
444 * TODO? For seekable streams, a forced could work around the problem. */
445 if (vlc_stream_Control(stream
->s
, STREAM_GET_PRIVATE_ID_STATE
, 0,
446 &(bool){ false }) == VLC_SUCCESS
)
449 stream_sys_t
*sys
= malloc(sizeof (*sys
));
450 if (unlikely(sys
== NULL
))
453 stream
->pf_read
= Read
;
454 stream
->pf_seek
= Seek
;
455 stream
->pf_control
= Control
;
457 vlc_stream_Control(stream
->s
, STREAM_CAN_SEEK
, &sys
->can_seek
);
458 vlc_stream_Control(stream
->s
, STREAM_CAN_PAUSE
, &sys
->can_pause
);
459 vlc_stream_Control(stream
->s
, STREAM_CAN_CONTROL_PACE
, &sys
->can_pace
);
460 if (vlc_stream_Control(stream
->s
, STREAM_GET_SIZE
, &sys
->size
))
462 vlc_stream_Control(stream
->s
, STREAM_GET_PTS_DELAY
, &sys
->pts_delay
);
463 if (vlc_stream_Control(stream
->s
, STREAM_GET_CONTENT_TYPE
,
465 sys
->content_type
= NULL
;
470 sys
->buffer_offset
= 0;
471 sys
->stream_offset
= 0;
472 sys
->buffer_length
= 0;
473 sys
->buffer_size
= var_InheritInteger(obj
, "prefetch-buffer-size") << 10u;
474 sys
->seek_threshold
= var_InheritInteger(obj
, "prefetch-seek-threshold");
475 sys
->controls
= NULL
;
477 uint64_t size
= stream_Size(stream
->s
);
479 { /* No point allocating a buffer larger than the source stream */
480 if (sys
->buffer_size
> size
)
481 sys
->buffer_size
= size
;
484 sys
->buffer
= malloc(sys
->buffer_size
);
485 if (sys
->buffer
== NULL
)
488 sys
->interrupt
= vlc_interrupt_create();
489 if (unlikely(sys
->interrupt
== NULL
))
492 vlc_mutex_init(&sys
->lock
);
493 vlc_cond_init(&sys
->wait_data
);
494 vlc_cond_init(&sys
->wait_space
);
498 if (vlc_clone(&sys
->thread
, Thread
, stream
, VLC_THREAD_PRIORITY_LOW
))
500 vlc_cond_destroy(&sys
->wait_space
);
501 vlc_cond_destroy(&sys
->wait_data
);
502 vlc_mutex_destroy(&sys
->lock
);
503 vlc_interrupt_destroy(sys
->interrupt
);
507 msg_Dbg(stream
, "using %zu bytes buffer", sys
->buffer_size
);
508 stream
->pf_read
= Read
;
509 stream
->pf_control
= Control
;
514 free(sys
->content_type
);
521 * Releases allocate resources.
523 static void Close (vlc_object_t
*obj
)
525 stream_t
*stream
= (stream_t
*)obj
;
526 stream_sys_t
*sys
= stream
->p_sys
;
528 vlc_cancel(sys
->thread
);
529 vlc_interrupt_kill(sys
->interrupt
);
530 vlc_join(sys
->thread
, NULL
);
531 vlc_interrupt_destroy(sys
->interrupt
);
532 vlc_cond_destroy(&sys
->wait_space
);
533 vlc_cond_destroy(&sys
->wait_data
);
534 vlc_mutex_destroy(&sys
->lock
);
538 struct stream_ctrl
*ctrl
= sys
->controls
;
539 sys
->controls
= ctrl
->next
;
543 free(sys
->content_type
);
548 set_category(CAT_INPUT
)
549 set_subcategory(SUBCAT_INPUT_STREAM_FILTER
)
550 set_capability("stream_filter", 0)
552 set_description(N_("Stream prefetch filter"))
553 set_callbacks(Open
, Close
)
555 add_integer("prefetch-buffer-size", 1 << 14, N_("Buffer size"),
556 N_("Prefetch buffer size (KiB)"), false)
557 change_integer_range(4, 1 << 20)
558 add_obsolete_integer("prefetch-read-size") /* since 4.0.0 */
559 add_integer("prefetch-seek-threshold", 1 << 14, N_("Seek threshold"),
560 N_("Prefetch forward seek threshold (bytes)"), true)
561 change_integer_range(0, UINT64_C(1) << 60)