1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 1999-2004 VLC authors and VideoLAN
6 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_stream.h>
34 #include <vlc_interrupt.h>
36 // #define STREAM_DEBUG 1
39 * Complex scheme using mutliple track to avoid seeking
42 /* How many tracks we have, currently only used for stream mode */
43 #ifdef OPTIMIZE_MEMORY
44 # define STREAM_CACHE_TRACK 1
45 /* Max size of our cache 128Ko per track */
46 # define STREAM_CACHE_SIZE (STREAM_CACHE_TRACK*1024*128)
48 # define STREAM_CACHE_TRACK 3
49 /* Max size of our cache 4Mo per track */
50 # define STREAM_CACHE_SIZE (4*STREAM_CACHE_TRACK*1024*1024)
53 /* How many data we try to prebuffer
54 * XXX it should be small to avoid useless latency but big enough for
55 * efficient demux probing */
56 #define STREAM_CACHE_PREBUFFER_SIZE (128)
59 * - We use ring buffers, only one if unseekable, all if seekable
60 * - Upon seek date current ring, then search if one ring match the pos,
61 * yes: switch to it, seek the access to match the end of the ring
62 * no: search the ring with i_end the closer to i_pos,
63 * if close enough, read data and use this ring
64 * else use the oldest ring, seek and use it.
66 * TODO: - with access non seekable: use all space available for only one ring, but
67 * we have to support seekable/non-seekable switch on the fly.
68 * - compute a good value for i_read_size
71 #define STREAM_READ_ATONCE 1024
72 #define STREAM_CACHE_TRACK_SIZE (STREAM_CACHE_SIZE/STREAM_CACHE_TRACK)
87 uint64_t i_pos
; /* Current reading offset */
89 unsigned i_offset
; /* Buffer offset in the current track */
90 int i_tk
; /* Current track */
91 stream_track_t tk
[STREAM_CACHE_TRACK
];
97 unsigned i_used
; /* Used since last read */
102 /* Stat about reading data */
103 uint64_t i_read_count
;
105 vlc_tick_t i_read_time
;
109 static int AStreamRefillStream(stream_t
*s
)
111 stream_sys_t
*sys
= s
->p_sys
;
112 stream_track_t
*tk
= &sys
->tk
[sys
->i_tk
];
114 /* We read but won't increase i_start after initial start + offset */
116 __MIN(sys
->i_used
, STREAM_CACHE_TRACK_SIZE
-
117 (tk
->i_end
- tk
->i_start
- sys
->i_offset
));
119 if (i_toread
<= 0) return VLC_SUCCESS
; /* EOF */
122 msg_Dbg(s
, "AStreamRefillStream: used=%d toread=%d",
123 sys
->i_used
, i_toread
);
126 vlc_tick_t start
= vlc_tick_now();
129 int i_off
= tk
->i_end
% STREAM_CACHE_TRACK_SIZE
;
135 i_read
= __MIN(i_toread
, STREAM_CACHE_TRACK_SIZE
- i_off
);
136 i_read
= vlc_stream_Read(s
->s
, &tk
->p_buffer
[i_off
], i_read
);
138 /* msg_Dbg(s, "AStreamRefillStream: read=%d", i_read); */
143 else if (i_read
== 0)
149 /* Windows of STREAM_CACHE_TRACK_SIZE */
150 if (tk
->i_start
+ STREAM_CACHE_TRACK_SIZE
< tk
->i_end
)
152 unsigned i_invalid
= tk
->i_end
- tk
->i_start
- STREAM_CACHE_TRACK_SIZE
;
154 tk
->i_start
+= i_invalid
;
155 sys
->i_offset
-= i_invalid
;
159 sys
->i_used
-= i_read
;
161 sys
->stat
.i_bytes
+= i_read
;
162 sys
->stat
.i_read_count
++;
165 sys
->stat
.i_read_time
+= vlc_tick_now() - start
;
169 static void AStreamPrebufferStream(stream_t
*s
)
171 stream_sys_t
*sys
= s
->p_sys
;
172 vlc_tick_t start
= vlc_tick_now();
175 msg_Dbg(s
, "starting pre-buffering");
178 stream_track_t
*tk
= &sys
->tk
[sys
->i_tk
];
179 vlc_tick_t now
= vlc_tick_now();
182 int i_buffered
= tk
->i_end
- tk
->i_start
;
184 if (vlc_killed() || i_buffered
>= STREAM_CACHE_PREBUFFER_SIZE
)
189 sys
->stat
.i_bytes
= i_buffered
;
190 sys
->stat
.i_read_time
= now
- start
;
191 i_byterate
= (CLOCK_FREQ
* sys
->stat
.i_bytes
) /
192 (sys
->stat
.i_read_time
+1);
194 msg_Dbg(s
, "pre-buffering done %"PRId64
" bytes in %"PRId64
"s - "
195 "%"PRId64
" KiB/s", sys
->stat
.i_bytes
,
196 SEC_FROM_VLC_TICK(sys
->stat
.i_read_time
), i_byterate
/ 1024);
200 i_read
= STREAM_CACHE_TRACK_SIZE
- i_buffered
;
201 i_read
= __MIN((int)sys
->i_read_size
, i_read
);
202 i_read
= vlc_stream_Read(s
->s
, &tk
->p_buffer
[i_buffered
], i_read
);
205 else if (i_read
== 0)
210 msg_Dbg(s
, "received first data after %"PRId64
" ms",
211 MS_FROM_VLC_TICK(vlc_tick_now() - start
));
216 sys
->stat
.i_read_count
++;
220 /****************************************************************************
221 * AStreamControlReset:
222 ****************************************************************************/
223 static void AStreamControlReset(stream_t
*s
)
225 stream_sys_t
*sys
= s
->p_sys
;
229 /* Setup our tracks */
234 for (unsigned i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
237 sys
->tk
[i
].i_start
= sys
->i_pos
;
238 sys
->tk
[i
].i_end
= sys
->i_pos
;
241 /* Do the prebuffering */
242 AStreamPrebufferStream(s
);
245 static ssize_t
AStreamReadStream(stream_t
*s
, void *buf
, size_t len
)
247 stream_sys_t
*sys
= s
->p_sys
;
248 stream_track_t
*tk
= &sys
->tk
[sys
->i_tk
];
250 if (tk
->i_start
>= tk
->i_end
)
254 msg_Dbg(s
, "AStreamReadStream: %zd pos=%"PRId64
" tk=%d start=%"PRId64
255 " offset=%d end=%"PRId64
, len
, sys
->i_pos
, sys
->i_tk
,
256 tk
->i_start
, sys
->i_offset
, tk
->i_end
);
259 unsigned i_off
= (tk
->i_start
+ sys
->i_offset
) % STREAM_CACHE_TRACK_SIZE
;
260 size_t i_current
= __MIN(tk
->i_end
- tk
->i_start
- sys
->i_offset
,
261 STREAM_CACHE_TRACK_SIZE
- i_off
);
262 ssize_t i_copy
= __MIN(i_current
, len
);
267 /* msg_Dbg(s, "AStreamReadStream: copy %zd", i_copy); */
269 memcpy(buf
, &tk
->p_buffer
[i_off
], i_copy
);
270 sys
->i_offset
+= i_copy
;
273 sys
->i_pos
+= i_copy
;
276 sys
->i_used
+= i_copy
;
278 if (tk
->i_end
+ i_copy
<= tk
->i_start
+ sys
->i_offset
+ len
)
280 const size_t i_read_requested
= VLC_CLIP(len
- i_copy
,
281 STREAM_READ_ATONCE
/ 2,
282 STREAM_READ_ATONCE
* 10);
283 if (sys
->i_used
< i_read_requested
)
284 sys
->i_used
= i_read_requested
;
286 AStreamRefillStream(s
);
292 static int AStreamSeekStream(stream_t
*s
, uint64_t i_pos
)
294 stream_sys_t
*sys
= s
->p_sys
;
295 stream_track_t
*p_current
= &sys
->tk
[sys
->i_tk
];
297 if (p_current
->i_start
>= p_current
->i_end
&& i_pos
>= p_current
->i_end
)
301 msg_Dbg(s
, "AStreamSeekStream: to %"PRId64
" pos=%"PRId64
302 " tk=%d start=%"PRId64
" offset=%d end=%"PRId64
,
303 i_pos
, sys
->i_pos
, sys
->i_tk
, p_current
->i_start
,
304 sys
->i_offset
, p_current
->i_end
);
308 vlc_stream_Control(s
->s
, STREAM_CAN_SEEK
, &b_aseek
);
309 if (!b_aseek
&& i_pos
< p_current
->i_start
)
311 msg_Warn(s
, "AStreamSeekStream: can't seek");
316 vlc_stream_Control(s
->s
, STREAM_CAN_FASTSEEK
, &b_afastseek
);
318 /* FIXME compute seek cost (instead of static 'stupid' value) */
319 uint64_t i_skip_threshold
;
321 i_skip_threshold
= b_afastseek
? 128 : 3 * sys
->i_read_size
;
323 i_skip_threshold
= INT64_MAX
;
325 /* Date the current track */
326 p_current
->date
= vlc_tick_now();
328 /* Search a new track slot */
329 stream_track_t
*tk
= NULL
;
332 /* Prefer the current track */
333 if (p_current
->i_start
<= i_pos
&& i_pos
<= p_current
->i_end
+ i_skip_threshold
)
336 i_tk_idx
= sys
->i_tk
;
340 /* Try to maximize already read data */
341 for (int i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
343 stream_track_t
*t
= &sys
->tk
[i
];
345 if (t
->i_start
> i_pos
|| i_pos
> t
->i_end
)
348 if (!tk
|| tk
->i_end
< t
->i_end
)
357 /* Use the oldest unused */
358 for (int i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
360 stream_track_t
*t
= &sys
->tk
[i
];
362 if (!tk
|| tk
->date
> t
->date
)
369 assert(i_tk_idx
>= 0 && i_tk_idx
< STREAM_CACHE_TRACK
);
372 i_skip_threshold
= 0;
373 if (tk
->i_start
<= i_pos
&& i_pos
<= tk
->i_end
+ i_skip_threshold
)
376 msg_Err(s
, "AStreamSeekStream: reusing %d start=%"PRId64
377 " end=%"PRId64
"(%s)",
378 i_tk_idx
, tk
->i_start
, tk
->i_end
,
379 tk
!= p_current
? "seek" : i_pos
> tk
->i_end
? "skip" : "noseek");
385 /* Seek at the end of the buffer
386 * TODO it is stupid to seek now, it would be better to delay it
388 if (vlc_stream_Seek(s
->s
, tk
->i_end
))
390 msg_Err(s
, "AStreamSeekStream: hard seek failed");
394 else if (i_pos
> tk
->i_end
)
396 uint64_t i_skip
= i_pos
- tk
->i_end
;
399 const int i_read_max
= __MIN(10 * STREAM_READ_ATONCE
, i_skip
);
401 if ((i_read
= AStreamReadStream(s
, NULL
, i_read_max
)) < 0)
403 msg_Err(s
, "AStreamSeekStream: skip failed");
405 } else if (i_read
== 0)
406 return VLC_SUCCESS
; /* EOF */
407 i_skip
-= i_read_max
;
414 msg_Err(s
, "AStreamSeekStream: hard seek");
416 /* Nothing good, seek and choose oldest segment */
417 if (vlc_stream_Seek(s
->s
, i_pos
))
419 msg_Err(s
, "AStreamSeekStream: hard seek failed");
426 sys
->i_offset
= i_pos
- tk
->i_start
;
427 sys
->i_tk
= i_tk_idx
;
430 /* If there is not enough data left in the track, refill */
431 /* TODO How to get a correct value for
432 * - refilling threshold
433 * - how much to refill
435 if (tk
->i_end
< tk
->i_start
+ sys
->i_offset
+ sys
->i_read_size
)
437 if (sys
->i_used
< STREAM_READ_ATONCE
/ 2)
438 sys
->i_used
= STREAM_READ_ATONCE
/ 2;
440 if (AStreamRefillStream(s
))
446 /****************************************************************************
448 ****************************************************************************/
449 static int AStreamControl(stream_t
*s
, int i_query
, va_list args
)
453 case STREAM_CAN_SEEK
:
454 case STREAM_CAN_FASTSEEK
:
455 case STREAM_CAN_PAUSE
:
456 case STREAM_CAN_CONTROL_PACE
:
457 case STREAM_GET_SIZE
:
458 case STREAM_GET_PTS_DELAY
:
459 case STREAM_GET_TITLE_INFO
:
460 case STREAM_GET_TITLE
:
461 case STREAM_GET_SEEKPOINT
:
462 case STREAM_GET_META
:
463 case STREAM_GET_CONTENT_TYPE
:
464 case STREAM_GET_SIGNAL
:
465 case STREAM_GET_TAGS
:
466 case STREAM_GET_TYPE
:
467 case STREAM_SET_PAUSE_STATE
:
468 case STREAM_SET_PRIVATE_ID_STATE
:
469 case STREAM_SET_PRIVATE_ID_CA
:
470 case STREAM_GET_PRIVATE_ID_STATE
:
471 return vlc_stream_vaControl(s
->s
, i_query
, args
);
473 case STREAM_SET_TITLE
:
474 case STREAM_SET_SEEKPOINT
:
476 int ret
= vlc_stream_vaControl(s
->s
, i_query
, args
);
477 if (ret
== VLC_SUCCESS
)
478 AStreamControlReset(s
);
482 case STREAM_SET_RECORD_STATE
:
484 msg_Err(s
, "invalid vlc_stream_vaControl query=0x%x", i_query
);
490 static int Open(vlc_object_t
*obj
)
492 stream_t
*s
= (stream_t
*)obj
;
494 if (s
->s
->pf_read
== NULL
)
497 stream_sys_t
*sys
= malloc(sizeof (*sys
));
498 if (unlikely(sys
== NULL
))
505 sys
->stat
.i_bytes
= 0;
506 sys
->stat
.i_read_time
= 0;
507 sys
->stat
.i_read_count
= 0;
509 msg_Dbg(s
, "Using stream method for AStream*");
511 /* Allocate/Setup our tracks */
514 sys
->p_buffer
= malloc(STREAM_CACHE_SIZE
);
515 if (sys
->p_buffer
== NULL
)
522 sys
->i_read_size
= STREAM_READ_ATONCE
;
523 #if STREAM_READ_ATONCE < 256
524 # error "Invalid STREAM_READ_ATONCE value"
527 for (unsigned i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
530 sys
->tk
[i
].i_start
= sys
->i_pos
;
531 sys
->tk
[i
].i_end
= sys
->i_pos
;
532 sys
->tk
[i
].p_buffer
= &sys
->p_buffer
[i
* STREAM_CACHE_TRACK_SIZE
];
537 /* Do the prebuffering */
538 AStreamPrebufferStream(s
);
540 if (sys
->tk
[sys
->i_tk
].i_end
<= 0)
542 msg_Err(s
, "cannot pre fill buffer");
548 s
->pf_read
= AStreamReadStream
;
549 s
->pf_seek
= AStreamSeekStream
;
550 s
->pf_control
= AStreamControl
;
554 /****************************************************************************
556 ****************************************************************************/
557 static void Close(vlc_object_t
*obj
)
559 stream_t
*s
= (stream_t
*)obj
;
560 stream_sys_t
*sys
= s
->p_sys
;
567 set_category(CAT_INPUT
)
568 set_subcategory(SUBCAT_INPUT_STREAM_FILTER
)
569 set_capability("stream_filter", 0)
570 add_shortcut("cache")
572 set_description(N_("Byte stream cache"))
573 set_callbacks(Open
, Close
)