You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
11 KiB
348 lines
11 KiB
/*
|
|
* Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
|
|
* 2001-2004 the dvdnav project
|
|
*
|
|
* This file is part of libdvdnav, a DVD navigation library.
|
|
*
|
|
* libdvdnav 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.
|
|
*
|
|
* libdvdnav 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
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*
|
|
* $Id: read_cache.c,v 1.12 2004/03/16 11:43:38 mroi Exp $
|
|
*
|
|
*/
|
|
/*
|
|
* There was a multithreaded read ahead cache in here for some time, but
|
|
* it had only been used for a short time. If you want to have a look at it,
|
|
* search the CVS attic.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "dvdnav.h"
|
|
#include "dvdnav_internal.h"
|
|
#include "read_cache.h"
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include "dvdread.h"
|
|
#define READ_CACHE_CHUNKS 10
|
|
|
|
/* all cache chunks must be memory aligned to allow use of raw devices */
|
|
#define ALIGNMENT 2048
|
|
|
|
#define READ_AHEAD_SIZE_MIN 4
|
|
#define READ_AHEAD_SIZE_MAX 512
|
|
|
|
typedef struct read_cache_chunk_s {
|
|
uint8_t *cache_buffer;
|
|
uint8_t *cache_buffer_base; /* used in malloc and free for alignment */
|
|
int32_t cache_start_sector; /* -1 means cache invalid */
|
|
int32_t cache_read_count; /* this many sectors are already read */
|
|
size_t cache_block_count; /* this many sectors will go in this chunk */
|
|
size_t cache_malloc_size;
|
|
int cache_valid;
|
|
int usage_count; /* counts how many buffers where issued from this chunk */
|
|
} read_cache_chunk_t;
|
|
|
|
struct read_cache_s {
|
|
read_cache_chunk_t chunk[READ_CACHE_CHUNKS];
|
|
int current;
|
|
int freeing; /* is set to one when we are about to dispose the cache */
|
|
uint32_t read_ahead_size;
|
|
int read_ahead_incr;
|
|
int last_sector;
|
|
pthread_mutex_t lock;
|
|
|
|
/* Bit of strange cross-linking going on here :) -- Gotta love C :) */
|
|
dvdnav_t *dvd_self;
|
|
};
|
|
|
|
/*
|
|
#define READ_CACHE_TRACE 0
|
|
*/
|
|
|
|
#ifdef __GNUC__
|
|
# if READ_CACHE_TRACE
|
|
# define dprintf(fmt, args...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt, __func__ , ## args)
|
|
# else
|
|
# define dprintf(fmt, args...) /* Nowt */
|
|
# endif
|
|
#else
|
|
# if READ_CACHE_TRACE
|
|
# define dprintf(fmt, ...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt, __func__ , __VA_ARGS__)
|
|
# else
|
|
#ifdef _MSC_VER
|
|
# define dprintf(fmt, str) /* Nowt */
|
|
#else
|
|
# define dprintf(fmt, ...) /* Nowt */
|
|
#endif /* _MSC_VER */
|
|
# endif
|
|
#endif
|
|
|
|
|
|
read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) {
|
|
read_cache_t *self;
|
|
int i;
|
|
|
|
self = (read_cache_t *)malloc(sizeof(read_cache_t));
|
|
|
|
if(self) {
|
|
self->current = 0;
|
|
self->freeing = 0;
|
|
self->dvd_self = dvd_self;
|
|
self->last_sector = 0;
|
|
self->read_ahead_size = READ_AHEAD_SIZE_MIN;
|
|
self->read_ahead_incr = 0;
|
|
pthread_mutex_init(&self->lock, NULL);
|
|
dvdnav_read_cache_clear(self);
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++) {
|
|
self->chunk[i].cache_buffer = NULL;
|
|
self->chunk[i].usage_count = 0;
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
void dvdnav_read_cache_free(read_cache_t* self) {
|
|
dvdnav_t *tmp;
|
|
int i;
|
|
|
|
pthread_mutex_lock(&self->lock);
|
|
self->freeing = 1;
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++)
|
|
if (self->chunk[i].cache_buffer && self->chunk[i].usage_count == 0) {
|
|
free(self->chunk[i].cache_buffer_base);
|
|
self->chunk[i].cache_buffer = NULL;
|
|
}
|
|
pthread_mutex_unlock(&self->lock);
|
|
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++)
|
|
if (self->chunk[i].cache_buffer) return;
|
|
|
|
/* all buffers returned, free everything */
|
|
tmp = self->dvd_self;
|
|
pthread_mutex_destroy(&self->lock);
|
|
free(self);
|
|
free(tmp);
|
|
}
|
|
|
|
/* This function MUST be called whenever self->file changes. */
|
|
void dvdnav_read_cache_clear(read_cache_t *self) {
|
|
int i;
|
|
|
|
if(!self)
|
|
return;
|
|
|
|
pthread_mutex_lock(&self->lock);
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++)
|
|
self->chunk[i].cache_valid = 0;
|
|
pthread_mutex_unlock(&self->lock);
|
|
}
|
|
|
|
/* This function is called just after reading the NAV packet. */
|
|
void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count) {
|
|
int i, use;
|
|
|
|
if(!self)
|
|
return;
|
|
|
|
if(!self->dvd_self->use_read_ahead)
|
|
return;
|
|
|
|
pthread_mutex_lock(&self->lock);
|
|
|
|
/* find a free cache chunk that best fits the required size */
|
|
use = -1;
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++)
|
|
if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
|
|
self->chunk[i].cache_malloc_size >= block_count &&
|
|
(use == -1 || self->chunk[use].cache_malloc_size > self->chunk[i].cache_malloc_size))
|
|
use = i;
|
|
|
|
if (use == -1) {
|
|
/* we haven't found a cache chunk, so we try to reallocate an existing one */
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++)
|
|
if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
|
|
(use == -1 || self->chunk[use].cache_malloc_size < self->chunk[i].cache_malloc_size))
|
|
use = i;
|
|
if (use >= 0) {
|
|
self->chunk[use].cache_buffer_base = realloc(self->chunk[use].cache_buffer_base,
|
|
block_count * DVD_VIDEO_LB_LEN + ALIGNMENT);
|
|
self->chunk[use].cache_buffer =
|
|
(uint8_t *)(((uintptr_t)self->chunk[use].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
|
|
dprintf("pre_cache DVD read realloc happened\n");
|
|
self->chunk[use].cache_malloc_size = block_count;
|
|
} else {
|
|
/* we still haven't found a cache chunk, let's allocate a new one */
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++)
|
|
if (!self->chunk[i].cache_buffer) {
|
|
use = i;
|
|
break;
|
|
}
|
|
if (use >= 0) {
|
|
/* We start with a sensible figure for the first malloc of 500 blocks.
|
|
* Some DVDs I have seen venture to 450 blocks.
|
|
* This is so that fewer realloc's happen if at all.
|
|
*/
|
|
self->chunk[i].cache_buffer_base =
|
|
malloc((block_count > 500 ? block_count : 500) * DVD_VIDEO_LB_LEN + ALIGNMENT);
|
|
self->chunk[i].cache_buffer =
|
|
(uint8_t *)(((uintptr_t)self->chunk[i].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
|
|
self->chunk[i].cache_malloc_size = block_count > 500 ? block_count : 500;
|
|
dprintf("pre_cache DVD read malloc %d blocks\n",
|
|
(block_count > 500 ? block_count : 500 ));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (use >= 0) {
|
|
self->chunk[use].cache_start_sector = sector;
|
|
self->chunk[use].cache_block_count = block_count;
|
|
self->chunk[use].cache_read_count = 0;
|
|
self->chunk[use].cache_valid = 1;
|
|
self->current = use;
|
|
} else {
|
|
dprintf("pre_caching was impossible, no cache chunk available\n");
|
|
}
|
|
pthread_mutex_unlock(&self->lock);
|
|
}
|
|
|
|
int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf) {
|
|
int i, use;
|
|
int start;
|
|
int size;
|
|
int incr;
|
|
uint8_t *read_ahead_buf;
|
|
int32_t res;
|
|
|
|
if(!self)
|
|
return 0;
|
|
|
|
use = -1;
|
|
|
|
if(self->dvd_self->use_read_ahead) {
|
|
/* first check, if sector is in current chunk */
|
|
read_cache_chunk_t cur = self->chunk[self->current];
|
|
if (cur.cache_valid && sector >= cur.cache_start_sector &&
|
|
sector <= (cur.cache_start_sector + cur.cache_read_count) &&
|
|
sector + block_count <= cur.cache_start_sector + cur.cache_block_count)
|
|
use = self->current;
|
|
else
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++)
|
|
if (self->chunk[i].cache_valid &&
|
|
sector >= self->chunk[i].cache_start_sector &&
|
|
sector <= (self->chunk[i].cache_start_sector + self->chunk[i].cache_read_count) &&
|
|
sector + block_count <= self->chunk[i].cache_start_sector + self->chunk[i].cache_block_count)
|
|
use = i;
|
|
}
|
|
|
|
if (use >= 0) {
|
|
read_cache_chunk_t *chunk;
|
|
|
|
/* Increment read-ahead size if sector follows the last sector */
|
|
if (sector == (self->last_sector + 1)) {
|
|
if (self->read_ahead_incr < READ_AHEAD_SIZE_MAX)
|
|
self->read_ahead_incr++;
|
|
} else {
|
|
self->read_ahead_size = READ_AHEAD_SIZE_MIN;
|
|
self->read_ahead_incr = 0;
|
|
}
|
|
self->last_sector = sector;
|
|
|
|
/* The following resources need to be protected by a mutex :
|
|
* self->chunk[*].cache_buffer
|
|
* self->chunk[*].cache_malloc_size
|
|
* self->chunk[*].usage_count
|
|
*/
|
|
pthread_mutex_lock(&self->lock);
|
|
chunk = &self->chunk[use];
|
|
read_ahead_buf = chunk->cache_buffer + chunk->cache_read_count * DVD_VIDEO_LB_LEN;
|
|
*buf = chunk->cache_buffer + (sector - chunk->cache_start_sector) * DVD_VIDEO_LB_LEN;
|
|
chunk->usage_count++;
|
|
pthread_mutex_unlock(&self->lock);
|
|
|
|
dprintf("libdvdnav: sector=%d, start_sector=%d, last_sector=%d\n", sector, chunk->cache_start_sector, chunk->cache_start_sector + chunk->cache_block_count);
|
|
|
|
/* read_ahead_size */
|
|
incr = self->read_ahead_incr >> 1;
|
|
if ((self->read_ahead_size + incr) > READ_AHEAD_SIZE_MAX) {
|
|
self->read_ahead_size = READ_AHEAD_SIZE_MAX;
|
|
} else {
|
|
self->read_ahead_size += incr;
|
|
}
|
|
|
|
/* real read size */
|
|
start = chunk->cache_start_sector + chunk->cache_read_count;
|
|
if (chunk->cache_read_count + self->read_ahead_size > chunk->cache_block_count) {
|
|
size = chunk->cache_block_count - chunk->cache_read_count;
|
|
} else {
|
|
size = self->read_ahead_size;
|
|
/* ensure that the sector we want will be read */
|
|
if (sector >= chunk->cache_start_sector + chunk->cache_read_count + size)
|
|
size = sector - chunk->cache_start_sector - chunk->cache_read_count;
|
|
}
|
|
dprintf("libdvdnav: read_ahead_size=%d, size=%d\n", self->read_ahead_size, size);
|
|
|
|
if (size)
|
|
chunk->cache_read_count += DVDReadBlocks(self->dvd_self->file,
|
|
start,
|
|
size,
|
|
read_ahead_buf);
|
|
|
|
res = DVD_VIDEO_LB_LEN * block_count;
|
|
|
|
} else {
|
|
|
|
if (self->dvd_self->use_read_ahead)
|
|
dprintf("cache miss on sector %d\n", sector);
|
|
|
|
res = DVDReadBlocks(self->dvd_self->file,
|
|
sector,
|
|
block_count,
|
|
*buf) * DVD_VIDEO_LB_LEN;
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) {
|
|
read_cache_t *cache;
|
|
int i;
|
|
|
|
if (!self)
|
|
return DVDNAV_STATUS_ERR;
|
|
|
|
cache = self->cache;
|
|
if (!cache)
|
|
return DVDNAV_STATUS_ERR;
|
|
|
|
pthread_mutex_lock(&cache->lock);
|
|
for (i = 0; i < READ_CACHE_CHUNKS; i++) {
|
|
if (cache->chunk[i].cache_buffer && buf >= cache->chunk[i].cache_buffer &&
|
|
buf < cache->chunk[i].cache_buffer + cache->chunk[i].cache_malloc_size * DVD_VIDEO_LB_LEN) {
|
|
cache->chunk[i].usage_count--;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&cache->lock);
|
|
|
|
if (cache->freeing)
|
|
/* when we want to dispose the cache, try freeing it now */
|
|
dvdnav_read_cache_free(cache);
|
|
|
|
return DVDNAV_STATUS_OK;
|
|
}
|