1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 1999-2004 VLC authors and VideoLAN
7 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_stream.h>
35 #include <vlc_interrupt.h>
37 // #define STREAM_DEBUG 1
40 * Complex scheme using mutliple track to avoid seeking
43 /* How many tracks we have, currently only used for stream mode */
44 #ifdef OPTIMIZE_MEMORY
45 # define STREAM_CACHE_TRACK 1
46 /* Max size of our cache 128Ko per track */
47 # define STREAM_CACHE_SIZE (STREAM_CACHE_TRACK*1024*128)
49 # define STREAM_CACHE_TRACK 3
50 /* Max size of our cache 4Mo per track */
51 # define STREAM_CACHE_SIZE (4*STREAM_CACHE_TRACK*1024*1024)
54 /* How many data we try to prebuffer
55 * XXX it should be small to avoid useless latency but big enough for
56 * efficient demux probing */
57 #define STREAM_CACHE_PREBUFFER_SIZE (128)
60 * - We use ring buffers, only one if unseekable, all if seekable
61 * - Upon seek date current ring, then search if one ring match the pos,
62 * yes: switch to it, seek the access to match the end of the ring
63 * no: search the ring with i_end the closer to i_pos,
64 * if close enough, read data and use this ring
65 * else use the oldest ring, seek and use it.
67 * TODO: - with access non seekable: use all space available for only one ring, but
68 * we have to support seekable/non-seekable switch on the fly.
69 * - compute a good value for i_read_size
72 #define STREAM_READ_ATONCE 1024
73 #define STREAM_CACHE_TRACK_SIZE (STREAM_CACHE_SIZE/STREAM_CACHE_TRACK)
88 uint64_t i_pos
; /* Current reading offset */
90 unsigned i_offset
; /* Buffer offset in the current track */
91 int i_tk
; /* Current track */
92 stream_track_t tk
[STREAM_CACHE_TRACK
];
98 unsigned i_used
; /* Used since last read */
103 /* Stat about reading data */
104 uint64_t i_read_count
;
106 uint64_t i_read_time
;
110 static int AStreamRefillStream(stream_t
*s
)
112 stream_sys_t
*sys
= s
->p_sys
;
113 stream_track_t
*tk
= &sys
->tk
[sys
->i_tk
];
115 /* We read but won't increase i_start after initial start + offset */
117 __MIN(sys
->i_used
, STREAM_CACHE_TRACK_SIZE
-
118 (tk
->i_end
- tk
->i_start
- sys
->i_offset
));
120 if (i_toread
<= 0) return VLC_SUCCESS
; /* EOF */
123 msg_Dbg(s
, "AStreamRefillStream: used=%d toread=%d",
124 sys
->i_used
, i_toread
);
127 mtime_t start
= mdate();
130 int i_off
= tk
->i_end
% STREAM_CACHE_TRACK_SIZE
;
136 i_read
= __MIN(i_toread
, STREAM_CACHE_TRACK_SIZE
- i_off
);
137 i_read
= vlc_stream_Read(s
->p_source
, &tk
->p_buffer
[i_off
], i_read
);
139 /* msg_Dbg(s, "AStreamRefillStream: read=%d", i_read); */
144 else if (i_read
== 0)
150 /* Windows of STREAM_CACHE_TRACK_SIZE */
151 if (tk
->i_start
+ STREAM_CACHE_TRACK_SIZE
< tk
->i_end
)
153 unsigned i_invalid
= tk
->i_end
- tk
->i_start
- STREAM_CACHE_TRACK_SIZE
;
155 tk
->i_start
+= i_invalid
;
156 sys
->i_offset
-= i_invalid
;
160 sys
->i_used
-= i_read
;
162 sys
->stat
.i_bytes
+= i_read
;
163 sys
->stat
.i_read_count
++;
166 sys
->stat
.i_read_time
+= mdate() - start
;
170 static void AStreamPrebufferStream(stream_t
*s
)
172 stream_sys_t
*sys
= s
->p_sys
;
173 mtime_t start
= mdate();
176 msg_Dbg(s
, "starting pre-buffering");
179 stream_track_t
*tk
= &sys
->tk
[sys
->i_tk
];
180 mtime_t now
= mdate();
183 int i_buffered
= tk
->i_end
- tk
->i_start
;
185 if (vlc_killed() || i_buffered
>= STREAM_CACHE_PREBUFFER_SIZE
)
190 sys
->stat
.i_bytes
= i_buffered
;
191 sys
->stat
.i_read_time
= now
- start
;
192 i_byterate
= (CLOCK_FREQ
* sys
->stat
.i_bytes
) /
193 (sys
->stat
.i_read_time
+1);
195 msg_Dbg(s
, "pre-buffering done %"PRId64
" bytes in %"PRId64
"s - "
196 "%"PRId64
" KiB/s", sys
->stat
.i_bytes
,
197 sys
->stat
.i_read_time
/ CLOCK_FREQ
, i_byterate
/ 1024);
201 i_read
= STREAM_CACHE_TRACK_SIZE
- i_buffered
;
202 i_read
= __MIN((int)sys
->i_read_size
, i_read
);
203 i_read
= vlc_stream_Read(s
->p_source
, &tk
->p_buffer
[i_buffered
],
207 else if (i_read
== 0)
212 msg_Dbg(s
, "received first data after %"PRId64
" ms",
213 (mdate() - start
) / 1000);
218 sys
->stat
.i_read_count
++;
222 /****************************************************************************
223 * AStreamControlReset:
224 ****************************************************************************/
225 static void AStreamControlReset(stream_t
*s
)
227 stream_sys_t
*sys
= s
->p_sys
;
231 /* Setup our tracks */
236 for (unsigned i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
239 sys
->tk
[i
].i_start
= sys
->i_pos
;
240 sys
->tk
[i
].i_end
= sys
->i_pos
;
243 /* Do the prebuffering */
244 AStreamPrebufferStream(s
);
247 static ssize_t
AStreamReadStream(stream_t
*s
, void *buf
, size_t len
)
249 stream_sys_t
*sys
= s
->p_sys
;
250 stream_track_t
*tk
= &sys
->tk
[sys
->i_tk
];
252 if (tk
->i_start
>= tk
->i_end
)
256 msg_Dbg(s
, "AStreamReadStream: %zd pos=%"PRId64
" tk=%d start=%"PRId64
257 " offset=%d end=%"PRId64
, len
, sys
->i_pos
, sys
->i_tk
,
258 tk
->i_start
, sys
->i_offset
, tk
->i_end
);
261 unsigned i_off
= (tk
->i_start
+ sys
->i_offset
) % STREAM_CACHE_TRACK_SIZE
;
262 size_t i_current
= __MIN(tk
->i_end
- tk
->i_start
- sys
->i_offset
,
263 STREAM_CACHE_TRACK_SIZE
- i_off
);
264 ssize_t i_copy
= __MIN(i_current
, len
);
269 /* msg_Dbg(s, "AStreamReadStream: copy %zd", i_copy); */
271 memcpy(buf
, &tk
->p_buffer
[i_off
], i_copy
);
272 sys
->i_offset
+= i_copy
;
275 sys
->i_pos
+= i_copy
;
278 sys
->i_used
+= i_copy
;
280 if (tk
->i_end
+ i_copy
<= tk
->i_start
+ sys
->i_offset
+ len
)
282 const size_t i_read_requested
= VLC_CLIP(len
- i_copy
,
283 STREAM_READ_ATONCE
/ 2,
284 STREAM_READ_ATONCE
* 10);
285 if (sys
->i_used
< i_read_requested
)
286 sys
->i_used
= i_read_requested
;
288 AStreamRefillStream(s
);
294 static int AStreamSeekStream(stream_t
*s
, uint64_t i_pos
)
296 stream_sys_t
*sys
= s
->p_sys
;
297 stream_track_t
*p_current
= &sys
->tk
[sys
->i_tk
];
299 if (p_current
->i_start
>= p_current
->i_end
&& i_pos
>= p_current
->i_end
)
303 msg_Dbg(s
, "AStreamSeekStream: to %"PRId64
" pos=%"PRId64
304 " tk=%d start=%"PRId64
" offset=%d end=%"PRId64
,
305 i_pos
, sys
->i_pos
, sys
->i_tk
, p_current
->i_start
,
306 sys
->i_offset
, p_current
->i_end
);
310 vlc_stream_Control(s
->p_source
, STREAM_CAN_SEEK
, &b_aseek
);
311 if (!b_aseek
&& i_pos
< p_current
->i_start
)
313 msg_Warn(s
, "AStreamSeekStream: can't seek");
318 vlc_stream_Control(s
->p_source
, STREAM_CAN_FASTSEEK
, &b_afastseek
);
320 /* FIXME compute seek cost (instead of static 'stupid' value) */
321 uint64_t i_skip_threshold
;
323 i_skip_threshold
= b_afastseek
? 128 : 3 * sys
->i_read_size
;
325 i_skip_threshold
= INT64_MAX
;
327 /* Date the current track */
328 p_current
->date
= mdate();
330 /* Search a new track slot */
331 stream_track_t
*tk
= NULL
;
334 /* Prefer the current track */
335 if (p_current
->i_start
<= i_pos
&& i_pos
<= p_current
->i_end
+ i_skip_threshold
)
338 i_tk_idx
= sys
->i_tk
;
342 /* Try to maximize already read data */
343 for (int i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
345 stream_track_t
*t
= &sys
->tk
[i
];
347 if (t
->i_start
> i_pos
|| i_pos
> t
->i_end
)
350 if (!tk
|| tk
->i_end
< t
->i_end
)
359 /* Use the oldest unused */
360 for (int i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
362 stream_track_t
*t
= &sys
->tk
[i
];
364 if (!tk
|| tk
->date
> t
->date
)
371 assert(i_tk_idx
>= 0 && i_tk_idx
< STREAM_CACHE_TRACK
);
374 i_skip_threshold
= 0;
375 if (tk
->i_start
<= i_pos
&& i_pos
<= tk
->i_end
+ i_skip_threshold
)
378 msg_Err(s
, "AStreamSeekStream: reusing %d start=%"PRId64
379 " end=%"PRId64
"(%s)",
380 i_tk_idx
, tk
->i_start
, tk
->i_end
,
381 tk
!= p_current
? "seek" : i_pos
> tk
->i_end
? "skip" : "noseek");
387 /* Seek at the end of the buffer
388 * TODO it is stupid to seek now, it would be better to delay it
390 if (vlc_stream_Seek(s
->p_source
, tk
->i_end
))
392 msg_Err(s
, "AStreamSeekStream: hard seek failed");
396 else if (i_pos
> tk
->i_end
)
398 uint64_t i_skip
= i_pos
- tk
->i_end
;
401 const int i_read_max
= __MIN(10 * STREAM_READ_ATONCE
, i_skip
);
403 if ((i_read
= AStreamReadStream(s
, NULL
, i_read_max
)) < 0)
405 msg_Err(s
, "AStreamSeekStream: skip failed");
407 } else if (i_read
== 0)
408 return VLC_SUCCESS
; /* EOF */
409 i_skip
-= i_read_max
;
416 msg_Err(s
, "AStreamSeekStream: hard seek");
418 /* Nothing good, seek and choose oldest segment */
419 if (vlc_stream_Seek(s
->p_source
, i_pos
))
421 msg_Err(s
, "AStreamSeekStream: hard seek failed");
428 sys
->i_offset
= i_pos
- tk
->i_start
;
429 sys
->i_tk
= i_tk_idx
;
432 /* If there is not enough data left in the track, refill */
433 /* TODO How to get a correct value for
434 * - refilling threshold
435 * - how much to refill
437 if (tk
->i_end
< tk
->i_start
+ sys
->i_offset
+ sys
->i_read_size
)
439 if (sys
->i_used
< STREAM_READ_ATONCE
/ 2)
440 sys
->i_used
= STREAM_READ_ATONCE
/ 2;
442 if (AStreamRefillStream(s
))
448 /****************************************************************************
450 ****************************************************************************/
451 static int AStreamControl(stream_t
*s
, int i_query
, va_list args
)
455 case STREAM_CAN_SEEK
:
456 case STREAM_CAN_FASTSEEK
:
457 case STREAM_CAN_PAUSE
:
458 case STREAM_CAN_CONTROL_PACE
:
459 case STREAM_IS_DIRECTORY
:
460 case STREAM_GET_SIZE
:
461 case STREAM_GET_PTS_DELAY
:
462 case STREAM_GET_TITLE_INFO
:
463 case STREAM_GET_TITLE
:
464 case STREAM_GET_SEEKPOINT
:
465 case STREAM_GET_META
:
466 case STREAM_GET_CONTENT_TYPE
:
467 case STREAM_GET_SIGNAL
:
468 case STREAM_SET_PAUSE_STATE
:
469 case STREAM_SET_PRIVATE_ID_STATE
:
470 case STREAM_SET_PRIVATE_ID_CA
:
471 case STREAM_GET_PRIVATE_ID_STATE
:
472 return vlc_stream_vaControl(s
->p_source
, i_query
, args
);
474 case STREAM_SET_TITLE
:
475 case STREAM_SET_SEEKPOINT
:
477 int ret
= vlc_stream_vaControl(s
->p_source
, i_query
, args
);
478 if (ret
== VLC_SUCCESS
)
479 AStreamControlReset(s
);
483 case STREAM_SET_RECORD_STATE
:
485 msg_Err(s
, "invalid vlc_stream_vaControl query=0x%x", i_query
);
491 static int Open(vlc_object_t
*obj
)
493 stream_t
*s
= (stream_t
*)obj
;
495 stream_sys_t
*sys
= malloc(sizeof (*sys
));
496 if (unlikely(sys
== NULL
))
503 sys
->stat
.i_bytes
= 0;
504 sys
->stat
.i_read_time
= 0;
505 sys
->stat
.i_read_count
= 0;
507 msg_Dbg(s
, "Using stream method for AStream*");
509 /* Allocate/Setup our tracks */
512 sys
->p_buffer
= malloc(STREAM_CACHE_SIZE
);
513 if (sys
->p_buffer
== NULL
)
520 sys
->i_read_size
= STREAM_READ_ATONCE
;
521 #if STREAM_READ_ATONCE < 256
522 # error "Invalid STREAM_READ_ATONCE value"
525 for (unsigned i
= 0; i
< STREAM_CACHE_TRACK
; i
++)
528 sys
->tk
[i
].i_start
= sys
->i_pos
;
529 sys
->tk
[i
].i_end
= sys
->i_pos
;
530 sys
->tk
[i
].p_buffer
= &sys
->p_buffer
[i
* STREAM_CACHE_TRACK_SIZE
];
535 /* Do the prebuffering */
536 AStreamPrebufferStream(s
);
538 if (sys
->tk
[sys
->i_tk
].i_end
<= 0)
540 msg_Err(s
, "cannot pre fill buffer");
546 s
->pf_read
= AStreamReadStream
;
547 s
->pf_seek
= AStreamSeekStream
;
548 s
->pf_control
= AStreamControl
;
552 /****************************************************************************
554 ****************************************************************************/
555 static void Close(vlc_object_t
*obj
)
557 stream_t
*s
= (stream_t
*)obj
;
558 stream_sys_t
*sys
= s
->p_sys
;
565 set_category(CAT_INPUT
)
566 set_subcategory(SUBCAT_INPUT_STREAM_FILTER
)
567 set_capability("stream_filter", 0)
569 set_description(N_("Byte stream cache"))
570 set_callbacks(Open
, Close
)