]> git.sesse.net Git - vlc/blob - src/misc/block.c
3cb537581be6e99c699baa426c7ea32dc01ed50f
[vlc] / src / misc / block.c
1 /*****************************************************************************
2  * block.c: Data blocks management functions
3  *****************************************************************************
4  * Copyright (C) 2003-2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32 #include <sys/stat.h>
33 #include "vlc_block.h"
34
35 /**
36  * @section Block handling functions.
37  */
38
39 /**
40  * Internal state for heap block.
41   */
42 struct block_sys_t
43 {
44     block_t     self;
45     size_t      i_allocated_buffer;
46     uint8_t     p_allocated_buffer[];
47 };
48
49 #ifndef NDEBUG
50 static void BlockNoRelease( block_t *b )
51 {
52     fprintf( stderr, "block %p has no release callback! This is a bug!\n", b );
53     abort();
54 }
55 #endif
56
57 void block_Init( block_t *restrict b, void *buf, size_t size )
58 {
59     /* Fill all fields to their default */
60     b->p_next = b->p_prev = NULL;
61     b->i_flags = 0;
62     b->i_pts = b->i_dts = b->i_length = 0;
63     b->i_rate = 0;
64     b->p_buffer = buf;
65     b->i_buffer = size;
66 #ifndef NDEBUG
67     b->pf_release = BlockNoRelease;
68 #endif
69 }
70
71 static void BlockRelease( block_t *p_block )
72 {
73     free( p_block );
74 }
75
76 /* Memory alignment */
77 #define BLOCK_ALIGN        16
78 /* Initial size of reserved header and footer */
79 #define BLOCK_PADDING_SIZE 32
80 /* Maximum size of reserved footer before we release with realloc() */
81 #define BLOCK_WASTE_SIZE   2048
82
83 block_t *block_Alloc( size_t i_size )
84 {
85     /* We do only one malloc
86      * TODO: bench if doing 2 malloc but keeping a pool of buffer is better
87      * TODO: use memalign
88      * 16 -> align on 16
89      * 2 * BLOCK_PADDING_SIZE -> pre + post padding
90      */
91     const size_t i_alloc = i_size + 2 * BLOCK_PADDING_SIZE + BLOCK_ALIGN;
92     block_sys_t *p_sys = malloc( sizeof( *p_sys ) + i_alloc );
93
94     if( p_sys == NULL )
95         return NULL;
96
97     /* Fill opaque data */
98     p_sys->i_allocated_buffer = i_alloc;
99
100     block_Init( &p_sys->self, p_sys->p_allocated_buffer + BLOCK_PADDING_SIZE
101                 + BLOCK_ALIGN
102                 - ((uintptr_t)p_sys->p_allocated_buffer % BLOCK_ALIGN),
103                 i_size );
104     p_sys->self.pf_release    = BlockRelease;
105
106     return &p_sys->self;
107 }
108
109 block_t *block_Realloc( block_t *p_block, ssize_t i_prebody, size_t i_body )
110 {
111     block_sys_t *p_sys = (block_sys_t *)p_block;
112     ssize_t i_buffer_size = i_prebody + i_body;
113
114     if( i_buffer_size <= 0 )
115     {
116         block_Release( p_block );
117         return NULL;
118     }
119
120     if( p_block->pf_release != BlockRelease )
121     {
122         /* Special case when pf_release if overloaded
123          * TODO if used one day, then implement it in a smarter way */
124         block_t *p_dup = block_Duplicate( p_block );
125         block_Release( p_block );
126         if( !p_dup )
127             return NULL;
128
129         p_block = p_dup;
130         p_sys = (block_sys_t *)p_block;
131     }
132
133     /* Adjust reserved header if there is enough room */
134     if( p_block->p_buffer - i_prebody > p_sys->p_allocated_buffer &&
135         p_block->p_buffer - i_prebody < p_sys->p_allocated_buffer +
136         p_sys->i_allocated_buffer )
137     {
138         p_block->p_buffer -= i_prebody;
139         p_block->i_buffer += i_prebody;
140         i_prebody = 0;
141     }
142
143     /* Adjust payload size if there is enough room */
144     if( p_block->p_buffer + i_body < p_sys->p_allocated_buffer +
145         p_sys->i_allocated_buffer )
146     {
147         p_block->i_buffer = i_buffer_size;
148         i_body = 0;
149     }
150
151     /* Not enough room, reallocate the buffer */
152     if( i_body > 0 || i_prebody > 0 )
153     {
154         /* FIXME: this is really dumb, we should use realloc() */
155         block_t *p_rea = block_New( NULL, i_buffer_size );
156
157         if( p_rea )
158         {
159             p_rea->i_dts     = p_block->i_dts;
160             p_rea->i_pts     = p_block->i_pts;
161             p_rea->i_flags   = p_block->i_flags;
162             p_rea->i_length  = p_block->i_length;
163             p_rea->i_rate    = p_block->i_rate;
164             p_rea->i_samples = p_block->i_samples;
165
166             memcpy( p_rea->p_buffer + i_prebody, p_block->p_buffer,
167                     __MIN( p_block->i_buffer, p_rea->i_buffer - i_prebody ) );
168         }
169
170         block_Release( p_block );
171
172         return p_rea;
173     }
174
175     /* We have a very large reserved footer now? Release some of it.
176      * XXX it may not keep the algniment of p_buffer */
177     if( (p_sys->p_allocated_buffer + p_sys->i_allocated_buffer) -
178         (p_block->p_buffer + p_block->i_buffer) > BLOCK_WASTE_SIZE )
179     {
180         const ptrdiff_t i_prebody = p_block->p_buffer - p_sys->p_allocated_buffer;
181         const size_t i_new = i_prebody + p_block->i_buffer + 1 * BLOCK_PADDING_SIZE;
182         block_sys_t *p_new = realloc( p_sys, sizeof (*p_sys) + i_new );
183
184         if( p_new != NULL )
185         {
186             p_sys = p_new;
187             p_sys->i_allocated_buffer = i_new;
188             p_block = &p_sys->self;
189             p_block->p_buffer = &p_sys->p_allocated_buffer[i_prebody];
190         }
191     }
192     return p_block;
193 }
194
195 #ifdef HAVE_MMAP
196 # include <sys/mman.h>
197
198 typedef struct block_mmap_t
199 {
200     block_t     self;
201     void       *base_addr;
202     size_t      length;
203 } block_mmap_t;
204
205 static void block_mmap_Release (block_t *block)
206 {
207     block_mmap_t *p_sys = (block_mmap_t *)block;
208
209     munmap (p_sys->base_addr, p_sys->length);
210     free (p_sys);
211 }
212
213 /**
214  * Creates a block from a virtual address memory mapping (mmap).
215  * This is provided by LibVLC so that mmap blocks can safely be deallocated
216  * even after the allocating plugin has been unloaded from memory.
217  *
218  * @param addr base address of the mapping (as returned by mmap)
219  * @param length length (bytes) of the mapping (as passed to mmap)
220  * @return NULL if addr is MAP_FAILED, or an error occurred (in the later
221  * case, munmap(addr, length) is invoked before returning).
222  */
223 block_t *block_mmap_Alloc (void *addr, size_t length)
224 {
225     if (addr == MAP_FAILED)
226         return NULL;
227
228     block_mmap_t *block = malloc (sizeof (*block));
229     if (block == NULL)
230     {
231         munmap (addr, length);
232         return NULL;
233     }
234
235     block_Init (&block->self, (uint8_t *)addr, length);
236     block->self.pf_release = block_mmap_Release;
237     block->base_addr = addr;
238     block->length = length;
239     return &block->self;
240 }
241 #else
242 block_t *block_mmap_Alloc (void *addr, size_t length)
243 {
244     (void)addr; (void)length; return NULL;
245 }
246 #endif
247
248
249 #ifdef WIN32
250 #ifdef UNDER_CE
251 #define _get_osfhandle(a) ((long) (a))
252 #endif
253
254 static
255 ssize_t pread (int fd, void *buf, size_t count, off_t offset)
256 {
257     HANDLE handle = (HANDLE)(intptr_t)_get_osfhandle (fd);
258     if (handle == INVALID_HANDLE_VALUE)
259         return -1;
260
261     OVERLAPPED olap = { .Offset = offset, .OffsetHigh = (offset >> 32), };
262     DWORD written;
263     /* This braindead API will override the file pointer even if we specify
264      * an explicit read offset... So do not expect this to mix well with
265      * regular read() calls. */
266     if (ReadFile (handle, buf, count, &written, &olap))
267         return written;
268     return -1;
269 }
270 #endif
271
272 /**
273  * Loads a file into a block of memory. If possible a private file mapping is
274  * created. Otherwise, the file is read normally. On 32-bits platforms, this
275  * function will not work for very large files, due to memory space
276  * constraints. Cancellation point.
277  *
278  * @param fd file descriptor to load from
279  * @return a new block with the file content at p_buffer, and file length at
280  * i_buffer (release it with block_Release()), or NULL upon error (see errno).
281  */
282 block_t *block_File (int fd)
283 {
284     size_t length;
285     struct stat st;
286
287     /* First, get the file size */
288     if (fstat (fd, &st))
289         return NULL;
290
291     /* st_size is meaningful for regular files, shared memory and typed memory.
292      * It's also meaning for symlinks, but that's not possible with fstat().
293      * In other cases, it's undefined, and we should really not go further. */
294 #ifndef S_TYPEISSHM
295 # define S_TYPEISSHM( buf ) (0)
296 #endif
297     if (S_ISDIR (st.st_mode))
298     {
299         errno = EISDIR;
300         return NULL;
301     }
302     if (!S_ISREG (st.st_mode) && !S_TYPEISSHM (&st))
303     {
304         errno = ESPIPE;
305         return NULL;
306     }
307
308     /* Prevent an integer overflow in mmap() and malloc() */
309     if (st.st_size >= SIZE_MAX)
310     {
311         errno = ENOMEM;
312         return NULL;
313     }
314     length = (size_t)st.st_size;
315
316 #ifdef HAVE_MMAP
317     if (length > 0)
318     {
319         void *addr;
320
321         addr = mmap (NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
322         if (addr != MAP_FAILED)
323             return block_mmap_Alloc (addr, length);
324     }
325 #endif
326
327     /* If mmap() is not implemented by the OS _or_ the filesystem... */
328     block_t *block = block_Alloc (length);
329     if (block == NULL)
330         return NULL;
331     block_cleanup_push (block);
332
333     for (size_t i = 0; i < length;)
334     {
335         ssize_t len = pread (fd, block->p_buffer + i, length - i, i);
336         if (len == -1)
337         {
338             block_Release (block);
339             block = NULL;
340             break;
341         }
342         i += len;
343     }
344     vlc_cleanup_pop ();
345     return block;
346 }
347
348 /**
349  * @section Thread-safe block queue functions
350  */
351
352 /**
353  * Internal state for block queues
354  */
355 struct block_fifo_t
356 {
357     vlc_mutex_t         lock;                         /* fifo data lock */
358     vlc_cond_t          wait;      /**< Wait for data */
359     vlc_cond_t          wait_room; /**< Wait for queue depth to shrink */
360
361     block_t             *p_first;
362     block_t             **pp_last;
363     size_t              i_depth;
364     size_t              i_size;
365     bool          b_force_wake;
366 };
367
368 block_fifo_t *block_FifoNew( void )
369 {
370     block_fifo_t *p_fifo = malloc( sizeof( block_fifo_t ) );
371     if( !p_fifo )
372         return NULL;
373
374     vlc_mutex_init( &p_fifo->lock );
375     vlc_cond_init( &p_fifo->wait );
376     vlc_cond_init( &p_fifo->wait_room );
377     p_fifo->p_first = NULL;
378     p_fifo->pp_last = &p_fifo->p_first;
379     p_fifo->i_depth = p_fifo->i_size = 0;
380     p_fifo->b_force_wake = false;
381
382     return p_fifo;
383 }
384
385 void block_FifoRelease( block_fifo_t *p_fifo )
386 {
387     block_FifoEmpty( p_fifo );
388     vlc_cond_destroy( &p_fifo->wait_room );
389     vlc_cond_destroy( &p_fifo->wait );
390     vlc_mutex_destroy( &p_fifo->lock );
391     free( p_fifo );
392 }
393
394 void block_FifoEmpty( block_fifo_t *p_fifo )
395 {
396     block_t *b;
397
398     vlc_mutex_lock( &p_fifo->lock );
399     for( b = p_fifo->p_first; b != NULL; )
400     {
401         block_t *p_next;
402
403         p_next = b->p_next;
404         block_Release( b );
405         b = p_next;
406     }
407
408     p_fifo->i_depth = p_fifo->i_size = 0;
409     p_fifo->p_first = NULL;
410     p_fifo->pp_last = &p_fifo->p_first;
411     vlc_cond_broadcast( &p_fifo->wait_room );
412     vlc_mutex_unlock( &p_fifo->lock );
413 }
414
415 /**
416  * Wait until the FIFO gets below a certain size (if needed).
417  *
418  * Note that if more than one thread writes to the FIFO, you cannot assume that
419  * the FIFO is actually below the requested size upon return (since another
420  * thread could have refilled it already). This is typically not an issue, as
421  * this function is meant for (relaxed) congestion control.
422  *
423  * This function may be a cancellation point and it is cancel-safe.
424  *
425  * @param fifo queue to wait on
426  * @param max_depth wait until the queue has no more than this many blocks
427  *                  (use SIZE_MAX to ignore this constraint)
428  * @param max_size wait until the queue has no more than this many bytes
429  *                  (use SIZE_MAX to ignore this constraint)
430  * @return nothing.
431  */
432 void block_FifoPace (block_fifo_t *fifo, size_t max_depth, size_t max_size)
433 {
434     vlc_testcancel ();
435
436     vlc_mutex_lock (&fifo->lock);
437     while ((fifo->i_depth > max_depth) || (fifo->i_size > max_size))
438     {
439          mutex_cleanup_push (&fifo->lock);
440          vlc_cond_wait (&fifo->wait_room, &fifo->lock);
441          vlc_cleanup_pop ();
442     }
443     vlc_mutex_unlock (&fifo->lock);
444 }
445
446 /**
447  * Immediately queue one block at the end of a FIFO.
448  * @param fifo queue
449  * @param block head of a block list to queue (may be NULL)
450  */
451 size_t block_FifoPut( block_fifo_t *p_fifo, block_t *p_block )
452 {
453     size_t i_size = 0;
454     vlc_mutex_lock( &p_fifo->lock );
455
456     while (p_block != NULL)
457     {
458         i_size += p_block->i_buffer;
459
460         *p_fifo->pp_last = p_block;
461         p_fifo->pp_last = &p_block->p_next;
462         p_fifo->i_depth++;
463         p_fifo->i_size += p_block->i_buffer;
464
465         p_block = p_block->p_next;
466     }
467
468     /* We queued one block: wake up one read-waiting thread */
469     vlc_cond_signal( &p_fifo->wait );
470     vlc_mutex_unlock( &p_fifo->lock );
471
472     return i_size;
473 }
474
475 void block_FifoWake( block_fifo_t *p_fifo )
476 {
477     vlc_mutex_lock( &p_fifo->lock );
478     if( p_fifo->p_first == NULL )
479         p_fifo->b_force_wake = true;
480     vlc_cond_broadcast( &p_fifo->wait );
481     vlc_mutex_unlock( &p_fifo->lock );
482 }
483
484 block_t *block_FifoGet( block_fifo_t *p_fifo )
485 {
486     block_t *b;
487
488     vlc_testcancel( );
489
490     vlc_mutex_lock( &p_fifo->lock );
491     mutex_cleanup_push( &p_fifo->lock );
492
493     /* Remember vlc_cond_wait() may cause spurious wakeups
494      * (on both Win32 and POSIX) */
495     while( ( p_fifo->p_first == NULL ) && !p_fifo->b_force_wake )
496         vlc_cond_wait( &p_fifo->wait, &p_fifo->lock );
497
498     vlc_cleanup_pop();
499     b = p_fifo->p_first;
500
501     p_fifo->b_force_wake = false;
502     if( b == NULL )
503     {
504         /* Forced wakeup */
505         vlc_mutex_unlock( &p_fifo->lock );
506         return NULL;
507     }
508
509     p_fifo->p_first = b->p_next;
510     p_fifo->i_depth--;
511     p_fifo->i_size -= b->i_buffer;
512
513     if( p_fifo->p_first == NULL )
514     {
515         p_fifo->pp_last = &p_fifo->p_first;
516     }
517
518     /* We don't know how many threads can queue new packets now. */
519     vlc_cond_broadcast( &p_fifo->wait_room );
520     vlc_mutex_unlock( &p_fifo->lock );
521
522     b->p_next = NULL;
523     return b;
524 }
525
526 block_t *block_FifoShow( block_fifo_t *p_fifo )
527 {
528     block_t *b;
529
530     vlc_testcancel( );
531
532     vlc_mutex_lock( &p_fifo->lock );
533     mutex_cleanup_push( &p_fifo->lock );
534
535     while( p_fifo->p_first == NULL )
536         vlc_cond_wait( &p_fifo->wait, &p_fifo->lock );
537
538     b = p_fifo->p_first;
539
540     vlc_cleanup_run ();
541     return b;
542 }
543
544 /* FIXME: not thread-safe */
545 size_t block_FifoSize( const block_fifo_t *p_fifo )
546 {
547     return p_fifo->i_size;
548 }
549
550 /* FIXME: not thread-safe */
551 size_t block_FifoCount( const block_fifo_t *p_fifo )
552 {
553     return p_fifo->i_depth;
554 }