1 /*****************************************************************************
2  * cache_block.c
3  *****************************************************************************
4  * Copyright (C) 1999-2004 VLC authors and VideoLAN
5  * $Id: 85c10203e13490bf937051deb9d69effafb6f8ec $
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23 
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 
28 #include <assert.h>
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_stream.h>
35 #include <vlc_interrupt.h>
36 
37 /* TODO:
38  *  - tune the 2 methods (block/stream)
39  *  - compute cost for seek
40  *  - improve stream mode seeking with closest segments
41  *  - ...
42  */
43 
44 /*
45  * One linked list of data read
46  */
47 
48 /* How many tracks we have, currently only used for stream mode */
49 #ifdef OPTIMIZE_MEMORY
50     /* Max size of our cache 128KiB per stream */
51 #   define STREAM_CACHE_SIZE  (1024*128)
52 #else
53     /* Max size of our cache 48MiB per stream */
54 #   define STREAM_CACHE_SIZE  (4*12*1024*1024)
55 #endif
56 
57 /* How many data we try to prebuffer
58  * XXX it should be small to avoid useless latency but big enough for
59  * efficient demux probing */
60 #define STREAM_CACHE_PREBUFFER_SIZE (128)
61 
62 /* Method: Simple, for pf_block.
63  *  We get blocks and put them in the linked list.
64  *  We release blocks once the total size is bigger than STREAM_CACHE_SIZE
65  */
66 
67 struct stream_sys_t
68 {
69     uint64_t     i_pos;      /* Current reading offset */
70 
71     uint64_t     i_start;        /* Offset of block for p_first */
72     uint64_t     i_offset;       /* Offset for data in p_current */
73     block_t     *p_current;     /* Current block */
74 
75     uint64_t     i_size;         /* Total amount of data in the list */
76     block_t     *p_first;
77     block_t    **pp_last;
78 
79     struct
80     {
81         /* Stat about reading data */
82         uint64_t i_read_count;
83         uint64_t i_bytes;
84         uint64_t i_read_time;
85     } stat;
86 };
87 
AStreamRefillBlock(stream_t * s)88 static int AStreamRefillBlock(stream_t *s)
89 {
90     stream_sys_t *sys = s->p_sys;
91 
92     /* Release data */
93     while (sys->i_size >= STREAM_CACHE_SIZE &&
94            sys->p_first != sys->p_current)
95     {
96         block_t *b = sys->p_first;
97 
98         sys->i_start += b->i_buffer;
99         sys->i_size  -= b->i_buffer;
100         sys->p_first  = b->p_next;
101 
102         block_Release(b);
103     }
104     if (sys->i_size >= STREAM_CACHE_SIZE &&
105         sys->p_current == sys->p_first &&
106         sys->p_current->p_next)    /* At least 2 packets */
107     {
108         /* Enough data, don't read more */
109         return VLC_SUCCESS;
110     }
111 
112     /* Now read a new block */
113     const mtime_t start = mdate();
114     block_t *b;
115 
116     for (;;)
117     {
118         if (vlc_killed())
119             return VLC_EGENERIC;
120 
121         /* Fetch a block */
122         if ((b = vlc_stream_ReadBlock(s->p_source)))
123             break;
124         if (vlc_stream_Eof(s->p_source))
125             return VLC_EGENERIC;
126     }
127 
128     sys->stat.i_read_time += mdate() - start;
129     while (b)
130     {
131         /* Append the block */
132         sys->i_size += b->i_buffer;
133         *sys->pp_last = b;
134         sys->pp_last = &b->p_next;
135 
136         /* Fix p_current */
137         if (sys->p_current == NULL)
138             sys->p_current = b;
139 
140         /* Update stat */
141         sys->stat.i_bytes += b->i_buffer;
142         sys->stat.i_read_count++;
143 
144         b = b->p_next;
145     }
146     return VLC_SUCCESS;
147 }
148 
AStreamPrebufferBlock(stream_t * s)149 static void AStreamPrebufferBlock(stream_t *s)
150 {
151     stream_sys_t *sys = s->p_sys;
152     mtime_t start = mdate();
153     bool first = true;
154 
155     msg_Dbg(s, "starting pre-buffering");
156     for (;;)
157     {
158         const int64_t now = mdate();
159 
160         if (vlc_killed() || sys->i_size > STREAM_CACHE_PREBUFFER_SIZE)
161         {
162             int64_t i_byterate;
163 
164             /* Update stat */
165             sys->stat.i_bytes = sys->i_size;
166             sys->stat.i_read_time = now - start;
167             i_byterate = (CLOCK_FREQ * sys->stat.i_bytes) /
168                          (sys->stat.i_read_time + 1);
169 
170             msg_Dbg(s, "prebuffering done %"PRId64" bytes in %"PRId64"s - "
171                      "%"PRId64" KiB/s",
172                      sys->stat.i_bytes,
173                      sys->stat.i_read_time / CLOCK_FREQ,
174                      i_byterate / 1024);
175             break;
176         }
177 
178         /* Fetch a block */
179         block_t *b = vlc_stream_ReadBlock(s->p_source);
180         if (b == NULL)
181         {
182             if (vlc_stream_Eof(s->p_source))
183                 break;
184             continue;
185         }
186 
187         while (b)
188         {
189             /* Append the block */
190             sys->i_size += b->i_buffer;
191             *sys->pp_last = b;
192             sys->pp_last = &b->p_next;
193 
194             sys->stat.i_read_count++;
195             b = b->p_next;
196         }
197 
198         if (first)
199         {
200             msg_Dbg(s, "received first data after %"PRId64" ms",
201                     (mdate() - start) / 1000);
202             first = false;
203         }
204     }
205 
206     sys->p_current = sys->p_first;
207 }
208 
209 /****************************************************************************
210  * AStreamControlReset:
211  ****************************************************************************/
AStreamControlReset(stream_t * s)212 static void AStreamControlReset(stream_t *s)
213 {
214     stream_sys_t *sys = s->p_sys;
215 
216     sys->i_pos = 0;
217 
218     block_ChainRelease(sys->p_first);
219 
220     /* Init all fields of sys->block */
221     sys->i_start = 0;
222     sys->i_offset = 0;
223     sys->p_current = NULL;
224     sys->i_size = 0;
225     sys->p_first = NULL;
226     sys->pp_last = &sys->p_first;
227 
228     /* Do the prebuffering */
229     AStreamPrebufferBlock(s);
230 }
231 
AStreamSeekBlock(stream_t * s,uint64_t i_pos)232 static int AStreamSeekBlock(stream_t *s, uint64_t i_pos)
233 {
234     stream_sys_t *sys = s->p_sys;
235     int64_t    i_offset = i_pos - sys->i_start;
236     bool b_seek;
237 
238     /* We already have thoses data, just update p_current/i_offset */
239     if (i_offset >= 0 && (uint64_t)i_offset < sys->i_size)
240     {
241         block_t *b = sys->p_first;
242         int i_current = 0;
243 
244         while (i_current + b->i_buffer < (uint64_t)i_offset)
245         {
246             i_current += b->i_buffer;
247             b = b->p_next;
248         }
249 
250         sys->p_current = b;
251         sys->i_offset = i_offset - i_current;
252 
253         sys->i_pos = i_pos;
254 
255         return VLC_SUCCESS;
256     }
257 
258     /* We may need to seek or to read data */
259     if (i_offset < 0)
260     {
261         bool b_aseek;
262         vlc_stream_Control(s->p_source, STREAM_CAN_SEEK, &b_aseek);
263 
264         if (!b_aseek)
265         {
266             msg_Err(s, "backward seeking impossible (access not seekable)");
267             return VLC_EGENERIC;
268         }
269 
270         b_seek = true;
271     }
272     else
273     {
274         bool b_aseek, b_aseekfast;
275 
276         vlc_stream_Control(s->p_source, STREAM_CAN_SEEK, &b_aseek);
277         vlc_stream_Control(s->p_source, STREAM_CAN_FASTSEEK, &b_aseekfast);
278 
279         if (!b_aseek)
280         {
281             b_seek = false;
282             msg_Warn(s, "%"PRId64" bytes need to be skipped "
283                       "(access non seekable)", i_offset - sys->i_size);
284         }
285         else
286         {
287             int64_t i_skip = i_offset - sys->i_size;
288 
289             /* Avg bytes per packets */
290             int i_avg = sys->stat.i_bytes / sys->stat.i_read_count;
291             /* TODO compute a seek cost instead of fixed threshold */
292             int i_th = b_aseekfast ? 1 : 5;
293 
294             if (i_skip <= i_th * i_avg &&
295                 i_skip < STREAM_CACHE_SIZE)
296                 b_seek = false;
297             else
298                 b_seek = true;
299 
300             msg_Dbg(s, "b_seek=%d th*avg=%d skip=%"PRId64,
301                      b_seek, i_th*i_avg, i_skip);
302         }
303     }
304 
305     if (b_seek)
306     {
307         /* Do the access seek */
308         if (vlc_stream_Seek(s->p_source, i_pos)) return VLC_EGENERIC;
309 
310         /* Release data */
311         block_ChainRelease(sys->p_first);
312 
313         /* Reinit */
314         sys->i_start = sys->i_pos = i_pos;
315         sys->i_offset = 0;
316         sys->p_current = NULL;
317         sys->i_size = 0;
318         sys->p_first = NULL;
319         sys->pp_last = &sys->p_first;
320 
321         /* Refill a block */
322         if (AStreamRefillBlock(s))
323             return VLC_EGENERIC;
324 
325         return VLC_SUCCESS;
326     }
327     else
328     {
329         do
330         {
331             while (sys->p_current &&
332                    sys->i_pos + sys->p_current->i_buffer - sys->i_offset <= i_pos)
333             {
334                 sys->i_pos += sys->p_current->i_buffer - sys->i_offset;
335                 sys->p_current = sys->p_current->p_next;
336                 sys->i_offset = 0;
337             }
338             if (!sys->p_current && AStreamRefillBlock(s))
339             {
340                 if (sys->i_pos != i_pos)
341                     return VLC_EGENERIC;
342             }
343         }
344         while (sys->i_start + sys->i_size < i_pos);
345 
346         sys->i_offset += i_pos - sys->i_pos;
347         sys->i_pos = i_pos;
348 
349         return VLC_SUCCESS;
350     }
351 
352     return VLC_EGENERIC;
353 }
354 
AStreamReadBlock(stream_t * s,void * buf,size_t len)355 static ssize_t AStreamReadBlock(stream_t *s, void *buf, size_t len)
356 {
357     stream_sys_t *sys = s->p_sys;
358 
359     /* It means EOF */
360     if (sys->p_current == NULL)
361         return 0;
362 
363     ssize_t i_current = sys->p_current->i_buffer - sys->i_offset;
364     size_t i_copy = VLC_CLIP((size_t)i_current, 0, len);
365 
366     /* Copy data */
367     memcpy(buf, &sys->p_current->p_buffer[sys->i_offset], i_copy);
368 
369     sys->i_offset += i_copy;
370     if (sys->i_offset >= sys->p_current->i_buffer)
371     {   /* Current block is now empty, switch to next */
372         sys->i_offset = 0;
373         sys->p_current = sys->p_current->p_next;
374 
375         /* Get a new block if needed */
376         if (sys->p_current == NULL)
377             AStreamRefillBlock(s);
378     }
379 
380     /**
381      * we should not signal end-of-file if we have not exhausted
382      * the blocks we know about, as such we should try again if that
383      * is the case. i_copy == 0 just means that the processed block does
384      * not contain data at the offset that we want, not EOF.
385      **/
386 
387     if( i_copy == 0 && sys->p_current )
388         return AStreamReadBlock( s, buf, len );
389 
390     sys->i_pos += i_copy;
391     return i_copy;
392 }
393 
394 /****************************************************************************
395  * AStreamControl:
396  ****************************************************************************/
AStreamControl(stream_t * s,int i_query,va_list args)397 static int AStreamControl(stream_t *s, int i_query, va_list args)
398 {
399     switch(i_query)
400     {
401         case STREAM_CAN_SEEK:
402         case STREAM_CAN_FASTSEEK:
403         case STREAM_CAN_PAUSE:
404         case STREAM_CAN_CONTROL_PACE:
405         case STREAM_IS_DIRECTORY:
406         case STREAM_GET_SIZE:
407         case STREAM_GET_PTS_DELAY:
408         case STREAM_GET_TITLE_INFO:
409         case STREAM_GET_TITLE:
410         case STREAM_GET_SEEKPOINT:
411         case STREAM_GET_META:
412         case STREAM_GET_CONTENT_TYPE:
413         case STREAM_GET_SIGNAL:
414         case STREAM_GET_TAGS:
415         case STREAM_SET_PAUSE_STATE:
416         case STREAM_SET_PRIVATE_ID_STATE:
417         case STREAM_SET_PRIVATE_ID_CA:
418         case STREAM_GET_PRIVATE_ID_STATE:
419             return vlc_stream_vaControl(s->p_source, i_query, args);
420 
421         case STREAM_SET_TITLE:
422         case STREAM_SET_SEEKPOINT:
423         {
424             int ret = vlc_stream_vaControl(s->p_source, i_query, args);
425             if (ret == VLC_SUCCESS)
426                 AStreamControlReset(s);
427             return ret;
428         }
429 
430         case STREAM_SET_RECORD_STATE:
431         default:
432             msg_Err(s, "invalid vlc_stream_vaControl query=0x%x", i_query);
433             return VLC_EGENERIC;
434     }
435     return VLC_SUCCESS;
436 }
437 
Open(vlc_object_t * obj)438 static int Open(vlc_object_t *obj)
439 {
440     stream_t *s = (stream_t *)obj;
441 
442     stream_sys_t *sys = malloc(sizeof (*sys));
443     if (unlikely(sys == NULL))
444         return VLC_ENOMEM;
445 
446     /* Common field */
447     sys->i_pos = 0;
448 
449     /* Stats */
450     sys->stat.i_bytes = 0;
451     sys->stat.i_read_time = 0;
452     sys->stat.i_read_count = 0;
453 
454     msg_Dbg(s, "Using block method for AStream*");
455 
456     /* Init all fields of sys->block */
457     sys->i_start = sys->i_pos;
458     sys->i_offset = 0;
459     sys->p_current = NULL;
460     sys->i_size = 0;
461     sys->p_first = NULL;
462     sys->pp_last = &sys->p_first;
463 
464     s->p_sys = sys;
465     /* Do the prebuffering */
466     AStreamPrebufferBlock(s);
467 
468     if (sys->i_size <= 0)
469     {
470         msg_Err(s, "cannot pre fill buffer");
471         free(sys);
472         return VLC_EGENERIC;
473     }
474 
475     s->pf_read = AStreamReadBlock;
476     s->pf_seek = AStreamSeekBlock;
477     s->pf_control = AStreamControl;
478     return VLC_SUCCESS;
479 }
480 
481 /****************************************************************************
482  * AStreamDestroy:
483  ****************************************************************************/
Close(vlc_object_t * obj)484 static void Close(vlc_object_t *obj)
485 {
486     stream_t *s = (stream_t *)obj;
487     stream_sys_t *sys = s->p_sys;
488 
489     block_ChainRelease(sys->p_first);
490     free(sys);
491 }
492 
493 vlc_module_begin()
494     set_category(CAT_INPUT)
495     set_subcategory(SUBCAT_INPUT_STREAM_FILTER)
496     set_capability("stream_filter", 0)
497 
498     set_description(N_("Block stream cache"))
499     set_callbacks(Open, Close)
500 vlc_module_end()
501