block.c 13.3 KB
Newer Older
1
/*****************************************************************************
gbazin's avatar
   
gbazin committed
2
 * block.c: Data blocks management functions
3
 *****************************************************************************
4
 * Copyright (C) 2003-2004 the VideoLAN team
5
 * $Id$
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 * Authors: Laurent Aimar <fenrir@videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
23
24
25
26
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
27
28
29
30
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

31
#include <vlc_common.h>
32
#include <sys/stat.h>
33
34
#include "vlc_block.h"

35
36
37
/*****************************************************************************
 * Block functions.
 *****************************************************************************/
38
39
40
/* private */
struct block_sys_t
{
41
    block_t     self;
42
    size_t      i_allocated_buffer;
43
    uint8_t     p_allocated_buffer[0];
44
45
};

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#ifndef NDEBUG
static void BlockNoRelease( block_t *b )
{
    fprintf( stderr, "block %p has no release callback! This is a bug!\n", b );
    abort();
}
#endif

void block_Init( block_t *restrict b, void *buf, size_t size )
{
    /* Fill all fields to their default */
    b->p_next = b->p_prev = NULL;
    b->i_flags = 0;
    b->i_pts = b->i_dts = b->i_length = 0;
    b->i_rate = 0;
    b->p_buffer = buf;
    b->i_buffer = size;
#ifndef NDEBUG
    b->pf_release = BlockNoRelease;
#endif
}

static void BlockRelease( block_t *p_block )
{
    free( p_block );
}

73
74
75
/* Memory alignment */
#define BLOCK_ALIGN        16
/* Initial size of reserved header and footer */
76
#define BLOCK_PADDING_SIZE 32
77
78
/* Maximum size of reserved footer before we release with realloc() */
#define BLOCK_WASTE_SIZE   2048
79

80
block_t *block_Alloc( size_t i_size )
81
{
82
    /* We do only one malloc
83
84
     * TODO: bench if doing 2 malloc but keeping a pool of buffer is better
     * TODO: use memalign
85
     * 16 -> align on 16
86
     * 2 * BLOCK_PADDING_SIZE -> pre + post padding
87
     */
88
    const size_t i_alloc = i_size + 2 * BLOCK_PADDING_SIZE + BLOCK_ALIGN;
89
    block_sys_t *p_sys = malloc( sizeof( *p_sys ) + i_alloc );
90

91
    if( p_sys == NULL )
Laurent Aimar's avatar
Laurent Aimar committed
92
        return NULL;
93

94
95
    /* Fill opaque data */
    p_sys->i_allocated_buffer = i_alloc;
96

97
    block_Init( &p_sys->self, p_sys->p_allocated_buffer + BLOCK_PADDING_SIZE
98
99
100
                + BLOCK_ALIGN
                - ((uintptr_t)p_sys->p_allocated_buffer % BLOCK_ALIGN),
                i_size );
101
    p_sys->self.pf_release    = BlockRelease;
102

103
    return &p_sys->self;
104
}
gbazin's avatar
   
gbazin committed
105

106
block_t *block_Realloc( block_t *p_block, ssize_t i_prebody, size_t i_body )
107
{
108
    block_sys_t *p_sys = (block_sys_t *)p_block;
109
110
111
112
113
114
115
    ssize_t i_buffer_size = i_prebody + i_body;

    if( i_buffer_size <= 0 )
    {
        block_Release( p_block );
        return NULL;
    }
116

117
    if( p_block->pf_release != BlockRelease )
118
    {
119
        /* Special case when pf_release if overloaded
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
120
         * TODO if used one day, then implement it in a smarter way */
121
122
        block_t *p_dup = block_Duplicate( p_block );
        block_Release( p_block );
Laurent Aimar's avatar
Laurent Aimar committed
123
124
        if( !p_dup )
            return NULL;
125
126

        p_block = p_dup;
127
128
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
129
    /* Adjust reserved header if there is enough room */
130
131
132
    if( p_block->p_buffer - i_prebody > p_sys->p_allocated_buffer &&
        p_block->p_buffer - i_prebody < p_sys->p_allocated_buffer +
        p_sys->i_allocated_buffer )
133
134
135
136
137
    {
        p_block->p_buffer -= i_prebody;
        p_block->i_buffer += i_prebody;
        i_prebody = 0;
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
138
139

    /* Adjust payload size if there is enough room */
140
141
    if( p_block->p_buffer + i_body < p_sys->p_allocated_buffer +
        p_sys->i_allocated_buffer )
142
    {
143
        p_block->i_buffer = i_buffer_size;
144
145
146
        i_body = 0;
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
147
    /* Not enough room, reallocate the buffer */
148
    if( i_body > 0 || i_prebody > 0 )
149
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
150
        /* FIXME: this is really dumb, we should use realloc() */
151
        block_t *p_rea = block_New( NULL, i_buffer_size );
152

Laurent Aimar's avatar
Laurent Aimar committed
153
154
155
156
157
158
159
160
161
162
163
164
        if( p_rea )
        {
            p_rea->i_dts     = p_block->i_dts;
            p_rea->i_pts     = p_block->i_pts;
            p_rea->i_flags   = p_block->i_flags;
            p_rea->i_length  = p_block->i_length;
            p_rea->i_rate    = p_block->i_rate;
            p_rea->i_samples = p_block->i_samples;

            memcpy( p_rea->p_buffer + i_prebody, p_block->p_buffer,
                    __MIN( p_block->i_buffer, p_rea->i_buffer - i_prebody ) );
        }
165

166
167
        block_Release( p_block );

168
169
170
        return p_rea;
    }

171
172
173
174
#if 0
    /* Shrinking the buffer seems to cause performance problems, on Windows,
     * at least. Also with demand paging, oversizing buffers is not an issue,
     * as long as we don't write to the extraneous allocated space. */
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
    /* We have a very large reserved footer now? Release some of it. */
    if ((p_sys->p_allocated_buffer + p_sys->i_allocated_buffer) -
        (p_block->p_buffer + p_block->i_buffer) > BLOCK_WASTE_SIZE)
    {
        const size_t news = p_block->i_buffer + 2 * BLOCK_PADDING_SIZE + 16;
        block_sys_t *newb = realloc (p_sys, sizeof (*p_sys) + news);

        if (newb != NULL)
        {
            p_sys = newb;
            p_sys->i_allocated_buffer = news;
            p_block = &p_sys->self;
            p_block->p_buffer = p_sys->p_allocated_buffer + BLOCK_PADDING_SIZE
                + BLOCK_ALIGN
                - ((uintptr_t)p_sys->p_allocated_buffer % BLOCK_ALIGN);
        }
    }
192
#endif
193
194
195
    return p_block;
}

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#ifdef HAVE_MMAP
# include <sys/mman.h>

typedef struct block_mmap_t
{
    block_t     self;
    void       *base_addr;
    size_t      length;
} block_mmap_t;

static void block_mmap_Release (block_t *block)
{
    block_mmap_t *p_sys = (block_mmap_t *)block;

    munmap (p_sys->base_addr, p_sys->length);
    free (p_sys);
}

214
215
216
217
218
219
220
221
222
223
/**
 * Creates a block from a virtual address memory mapping (mmap).
 * This is provided by LibVLC so that mmap blocks can safely be deallocated
 * even after the allocating plugin has been unloaded from memory.
 *
 * @param addr base address of the mapping (as returned by mmap)
 * @param length length (bytes) of the mapping (as passed to mmap)
 * @return NULL if addr is MAP_FAILED, or an error occurred (in the later
 * case, munmap(addr, length) is invoked before returning).
 */
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
block_t *block_mmap_Alloc (void *addr, size_t length)
{
    if (addr == MAP_FAILED)
        return NULL;

    block_mmap_t *block = malloc (sizeof (*block));
    if (block == NULL)
    {
        munmap (addr, length);
        return NULL;
    }

    block_Init (&block->self, (uint8_t *)addr, length);
    block->self.pf_release = block_mmap_Release;
    block->base_addr = addr;
    block->length = length;
    return &block->self;
}
242
243
244
245
246
#else
block_t *block_mmap_Alloc (void *addr, size_t length)
{
    (void)addr; (void)length; return NULL;
}
247
248
#endif

249

250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#ifdef WIN32
static
ssize_t pread (int fd, void *buf, size_t count, off_t offset)
{
    HANDLE handle = (HANDLE)(intptr_t)_get_osfhandle (fd);
    if (handle == INVALID_HANDLE_VALUE)
        return -1;

    OVERLAPPED olap = { .Offset = offset, .OffsetHigh = (offset >> 32), };
    DWORD written;
    /* This braindead API will override the file pointer even if we specify
     * an explicit read offset... So do not expect this to mix well with
     * regular read() calls. */
    if (ReadFile (handle, buf, count, &written, &olap))
        return written;
    return -1;
}
#endif

269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/**
 * Loads a file into a block of memory. If possible a private file mapping is
 * created. Otherwise, the file is read normally. On 32-bits platforms, this
 * function will not work for very large files, due to memory space
 * constraints.
 *
 * @param fd file descriptor to load from
 * @return a new block with the file content at p_buffer, and file length at
 * i_buffer (release it with block_Release()), or NULL upon error (see errno).
 */
block_t *block_File (int fd)
{
    size_t length;
    struct stat st;

    /* First, get the file size */
    if (fstat (fd, &st))
        return NULL;

    /* st_size is meaningful for regular files, shared memory and typed memory.
     * It's also meaning for symlinks, but that's not possible with fstat().
     * In other cases, it's undefined, and we should really not go further. */
#ifndef S_TYPEISSHM
# define S_TYPEISSHM( buf ) (0)
#endif
    if (S_ISDIR (st.st_mode))
    {
        errno = EISDIR;
        return NULL;
    }
    if (!S_ISREG (st.st_mode) && !S_TYPEISSHM (&st))
    {
        errno = ESPIPE;
        return NULL;
    }

    /* Prevent an integer overflow in mmap() and malloc() */
    if (st.st_size >= SIZE_MAX)
    {
        errno = ENOMEM;
        return NULL;
    }
    length = (size_t)st.st_size;

#ifdef HAVE_MMAP
    if (length > 0)
    {
        void *addr;

        addr = mmap (NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
        if (addr != MAP_FAILED)
            return block_mmap_Alloc (addr, length);
    }
#endif

    /* If mmap() is not implemented by the OS _or_ the filesystem... */
    block_t *block = block_Alloc (length);
    if (block == NULL)
        return NULL;

    for (size_t i = 0; i < length;)
    {
        ssize_t len = pread (fd, block->p_buffer + i, length - i, i);
        if (len == -1)
        {
            block_Release (block);
            return NULL;
        }
        i += len;
    }
    return block;
}

342
/*****************************************************************************
gbazin's avatar
   
gbazin committed
343
 * block_fifo_t management
344
 *****************************************************************************/
345
346
347
348
349
350
351
struct block_fifo_t
{
    vlc_mutex_t         lock;                         /* fifo data lock */
    vlc_cond_t          wait;         /* fifo data conditional variable */

    block_t             *p_first;
    block_t             **pp_last;
352
    size_t              i_depth;
353
    size_t              i_size;
354
    bool          b_force_wake;
355
356
};

357
block_fifo_t *block_FifoNew( void )
358
{
359
360
361
    block_fifo_t *p_fifo = malloc( sizeof( block_fifo_t ) );
    if( !p_fifo )
        return NULL;
362

363
    vlc_mutex_init( &p_fifo->lock );
364
    vlc_cond_init( NULL, &p_fifo->wait );
365
366
    p_fifo->p_first = NULL;
    p_fifo->pp_last = &p_fifo->p_first;
367
    p_fifo->i_depth = p_fifo->i_size = 0;
368
    p_fifo->b_force_wake = false;
369
370
371
372

    return p_fifo;
}

gbazin's avatar
   
gbazin committed
373
void block_FifoRelease( block_fifo_t *p_fifo )
374
375
376
377
378
379
380
{
    block_FifoEmpty( p_fifo );
    vlc_cond_destroy( &p_fifo->wait );
    vlc_mutex_destroy( &p_fifo->lock );
    free( p_fifo );
}

gbazin's avatar
   
gbazin committed
381
void block_FifoEmpty( block_fifo_t *p_fifo )
382
383
384
385
386
387
388
389
390
391
392
393
394
{
    block_t *b;

    vlc_mutex_lock( &p_fifo->lock );
    for( b = p_fifo->p_first; b != NULL; )
    {
        block_t *p_next;

        p_next = b->p_next;
        block_Release( b );
        b = p_next;
    }

395
    p_fifo->i_depth = p_fifo->i_size = 0;
396
397
398
399
400
    p_fifo->p_first = NULL;
    p_fifo->pp_last = &p_fifo->p_first;
    vlc_mutex_unlock( &p_fifo->lock );
}

401
size_t block_FifoPut( block_fifo_t *p_fifo, block_t *p_block )
402
{
403
    size_t i_size = 0;
404
405
406
407
408
409
410
411
412
    vlc_mutex_lock( &p_fifo->lock );

    do
    {
        i_size += p_block->i_buffer;

        *p_fifo->pp_last = p_block;
        p_fifo->pp_last = &p_block->p_next;
        p_fifo->i_depth++;
413
        p_fifo->i_size += p_block->i_buffer;
414
415
416
417
418

        p_block = p_block->p_next;

    } while( p_block );

gbazin's avatar
   
gbazin committed
419
    /* warn there is data in this fifo */
420
421
422
423
424
425
    vlc_cond_signal( &p_fifo->wait );
    vlc_mutex_unlock( &p_fifo->lock );

    return i_size;
}

426
427
428
429
void block_FifoWake( block_fifo_t *p_fifo )
{
    vlc_mutex_lock( &p_fifo->lock );
    if( p_fifo->p_first == NULL )
430
        p_fifo->b_force_wake = true;
431
432
433
434
    vlc_cond_signal( &p_fifo->wait );
    vlc_mutex_unlock( &p_fifo->lock );
}

gbazin's avatar
   
gbazin committed
435
block_t *block_FifoGet( block_fifo_t *p_fifo )
436
437
438
439
440
{
    block_t *b;

    vlc_mutex_lock( &p_fifo->lock );

441
442
443
    /* Remember vlc_cond_wait() may cause spurious wakeups
     * (on both Win32 and POSIX) */
    while( ( p_fifo->p_first == NULL ) && !p_fifo->b_force_wake )
444
445
446
447
448
449
    {
        vlc_cond_wait( &p_fifo->wait, &p_fifo->lock );
    }

    b = p_fifo->p_first;

450
    p_fifo->b_force_wake = false;
451
452
453
454
455
456
457
    if( b == NULL )
    {
        /* Forced wakeup */
        vlc_mutex_unlock( &p_fifo->lock );
        return NULL;
    }

458
459
    p_fifo->p_first = b->p_next;
    p_fifo->i_depth--;
460
    p_fifo->i_size -= b->i_buffer;
461
462
463
464
465
466
467
468
469

    if( p_fifo->p_first == NULL )
    {
        p_fifo->pp_last = &p_fifo->p_first;
    }

    vlc_mutex_unlock( &p_fifo->lock );

    b->p_next = NULL;
470
    return b;
471
472
}

gbazin's avatar
   
gbazin committed
473
block_t *block_FifoShow( block_fifo_t *p_fifo )
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
{
    block_t *b;

    vlc_mutex_lock( &p_fifo->lock );

    if( p_fifo->p_first == NULL )
    {
        vlc_cond_wait( &p_fifo->wait, &p_fifo->lock );
    }

    b = p_fifo->p_first;

    vlc_mutex_unlock( &p_fifo->lock );

    return( b );
489
}
490

491
492
493
size_t block_FifoSize( const block_fifo_t *p_fifo )
{
    return p_fifo->i_size;
494
495
}

496
497
498
499
size_t block_FifoCount( const block_fifo_t *p_fifo )
{
    return p_fifo->i_depth;
}