summaryrefslogtreecommitdiff
path: root/src/input/libdvdnav/read_cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/input/libdvdnav/read_cache.c')
-rw-r--r--src/input/libdvdnav/read_cache.c543
1 files changed, 543 insertions, 0 deletions
diff --git a/src/input/libdvdnav/read_cache.c b/src/input/libdvdnav/read_cache.c
new file mode 100644
index 000000000..1d6e68727
--- /dev/null
+++ b/src/input/libdvdnav/read_cache.c
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * $Id: read_cache.c,v 1.1 2002/08/08 17:49:21 richwareham Exp $
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dvdnav.h"
+#include "read_cache.h"
+#include <pthread.h>
+#include <sys/time.h>
+#include <time.h>
+
+/*
+#define DVDNAV_PROFILE
+*/
+
+/* Read-ahead cache structure. */
+#if _MULTITHREAD_
+
+/* For the multithreaded cache, the cache is a ring buffer + writing
+ * thread that continuously reads data into the buffer until it is
+ * full or the 'upper-bound' has been reached.
+ */
+
+#define CACHE_BUFFER_SIZE 2048 /* Cache this number of blocks at a time */
+
+struct read_cache_s {
+ pthread_mutex_t cache_lock;
+ pthread_t read_thread;
+
+ /* Buffer */
+ uint8_t *buffer;
+
+ /* Size of buffer */
+ int32_t size;
+ /* block offset from sector start of buffer 'head' */
+ uint32_t pos;
+ /* block offset from sector start of read point */
+ uint32_t read_point;
+ /* block offset from buffer start to ring-boundary */
+ uint32_t start;
+
+ /* Bit of strange cross-linking going on here :) -- Gotta love C :) */
+ dvdnav_t *dvd_self;
+};
+
+#else
+
+#define READ_CACHE_CHUNKS 10
+
+typedef struct read_cache_chunk_s {
+ uint8_t *cache_buffer;
+ int32_t cache_start_sector; /* -1 means cache invalid */
+ size_t cache_block_count;
+ 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 */
+ pthread_mutex_t lock;
+
+ /* Bit of strange cross-linking going on here :) -- Gotta love C :) */
+ dvdnav_t *dvd_self;
+};
+#endif
+
+#define _MT_TRACE 0
+
+#if _MT_TRACE
+#define dprintf(fmt, args...) fprintf(stderr, "%s: "fmt, __func__ , ## args);
+#else
+#define dprintf(fmt, args...) /* Nowt */
+#endif
+
+#if _MULTITHREAD_
+
+void * read_cache_read_thread (void * this_gen) {
+ int cont = 1;
+ int32_t diff, start;
+ uint32_t pos, size, startp, endp;
+ uint32_t s,c;
+ uint8_t *at;
+ read_cache_t *self = (read_cache_t*)this_gen;
+
+ while(cont) {
+
+ pthread_mutex_lock(&self->cache_lock);
+
+ if(self->size >= 0) {
+ diff = self->read_point - self->pos;
+ if(diff >= self->size/2) {
+ dprintf("(II) Read thread -- ");
+
+ startp = (self->start) % CACHE_BUFFER_SIZE;
+ endp = abs((self->start + diff - 1) % CACHE_BUFFER_SIZE);
+ dprintf("startp = %i, endp = %i -- ",startp, endp);
+
+ pos = self->pos + diff;
+ size = self->size - diff;
+ start = (self->start + diff) % CACHE_BUFFER_SIZE;
+
+ /* Fill remainder of buffer */
+
+ if(startp > endp) {
+ s = pos + size; c = CACHE_BUFFER_SIZE - startp;
+ at = self->buffer + (startp * DVD_VIDEO_LB_LEN);
+ if(c > 0) {
+ dprintf("(1) Reading from %i to %i to %i ", s, s+c-1, startp);
+ pthread_mutex_unlock(&self->cache_lock);
+ DVDReadBlocks(self->dvd_self->file, s,c, at);
+ pthread_mutex_lock(&self->cache_lock);
+ }
+
+ s = pos + size + c; c = CACHE_BUFFER_SIZE - size - c;
+ at = self->buffer;
+ if(c > 0) {
+ dprintf("(2) Reading from %i to %i to %i ", s, s+c-1, 0);
+ pthread_mutex_unlock(&self->cache_lock);
+ DVDReadBlocks(self->dvd_self->file, s,c, at);
+ pthread_mutex_lock(&self->cache_lock);
+ }
+ } else {
+ s = pos + size; c = CACHE_BUFFER_SIZE - size;
+ at = self->buffer + (startp * DVD_VIDEO_LB_LEN);
+ if(c > 0) {
+ dprintf("(3) Reading from %i to %i to %i ", s, s+c-1, startp);
+ pthread_mutex_unlock(&self->cache_lock);
+ DVDReadBlocks(self->dvd_self->file, s,c, at);
+ pthread_mutex_lock(&self->cache_lock);
+ }
+ }
+
+ dprintf("\n");
+
+ self->pos = pos;
+ self->start = start; self->size = CACHE_BUFFER_SIZE;
+ }
+ }
+
+ pthread_mutex_unlock(&self->cache_lock);
+ cont = (self->buffer != NULL);
+ usleep(100);
+ }
+
+ return NULL;
+}
+
+read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) {
+ read_cache_t *me;
+
+ me = (read_cache_t*)malloc(sizeof(struct read_cache_s));
+
+ if(me) {
+ int err;
+
+ me->dvd_self = dvd_self;
+ me->buffer = (uint8_t*)malloc(CACHE_BUFFER_SIZE * DVD_VIDEO_LB_LEN);
+ me->start = 0;
+ me->pos = 0;
+ me->read_point = 0;
+ me->size = -1;
+
+ /* Initialise the mutex */
+ pthread_mutex_init(&me->cache_lock, NULL);
+
+ if ((err = pthread_create (&me->read_thread,
+ NULL, read_cache_read_thread, me)) != 0) {
+ dprintf("read_cache: can't create new thread (%s)\n",strerror(err));
+ }
+ }
+
+ return me;
+}
+
+void dvdnav_read_cache_free(read_cache_t* self) {
+ dvdnav_t *tmp;
+
+ pthread_mutex_lock(&self->cache_lock);
+
+ if(self->buffer) {
+ free(self->buffer);
+ self->buffer = NULL;
+ self->size = -2;
+ }
+
+ pthread_mutex_unlock(&self->cache_lock);
+
+ pthread_join(self->read_thread, NULL);
+
+ pthread_mutex_destroy(&self->cache_lock);
+
+ tmp = self->dvd_self;
+ free(self);
+
+ /* We free the main structure, too, because we have no buffers out there. */
+ free(tmp);
+}
+
+/* This function MUST be called whenever self->file changes. */
+void dvdnav_read_cache_clear(read_cache_t *self) {
+ if(!self)
+ return;
+
+ pthread_mutex_lock(&self->cache_lock);
+ self->size = -1;
+ self->start = 0;
+ self->pos = 0;
+ self->read_point = 0;
+ pthread_mutex_unlock(&self->cache_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) {
+ if(!self)
+ return;
+
+ if(!self->dvd_self->use_read_ahead) {
+ return;
+ }
+
+ pthread_mutex_lock(&self->cache_lock);
+ dprintf("Requested pre-cache (%i -> +%i) : current state pos=%i, size=%i.\n",
+ sector, block_count, self->pos, self->size);
+
+ /* Are the contents of the buffer in any way relevant? */
+ if((self->size > 0) && (sector >= self->pos) && (sector <= self->pos+self->size)) {
+ dprintf("Contents relevant ... adjusting\n");
+ self->read_point = sector;
+ } else {
+ /* Flush the cache as its not much use */
+ dprintf("Contents irrelevent... flushing\n");
+ self->size = 0;
+ self->start = 0;
+ self->pos = sector;
+ self->read_point = sector;
+ }
+
+ pthread_mutex_unlock(&self->cache_lock);
+}
+
+/* This function will do the cache read once implemented */
+int dvdnav_read_cache_block( read_cache_t *self, int sector, size_t block_count, uint8_t **buf) {
+ int result, diff;
+
+ if(!self)
+ return 0;
+
+ pthread_mutex_lock(&self->cache_lock);
+ dprintf("Read from %i -> +%i (buffer pos=%i, read_point=%i, size=%i)... ", sector, block_count,
+ self->pos, self->read_point, self->size);
+ if((self->size > 0) && (sector >= self->read_point) &&
+ (sector + block_count <= self->pos + self->size)) {
+ /* Hit */
+
+ /* Drop any skipped blocks */
+ diff = sector - self->read_point;
+ if(diff > 0)
+ self->read_point += diff;
+
+ diff = self->read_point - self->pos;
+
+ if(((self->start + diff) % CACHE_BUFFER_SIZE) + block_count <= CACHE_BUFFER_SIZE) {
+ dprintf("************** Single read\n");
+ memcpy(*buf, self->buffer + (((self->start + diff) % CACHE_BUFFER_SIZE) * DVD_VIDEO_LB_LEN),
+ block_count * DVD_VIDEO_LB_LEN);
+ self->read_point += block_count;
+ pthread_mutex_unlock(&self->cache_lock);
+
+ return (int)block_count;
+ } else {
+ int32_t boundary = CACHE_BUFFER_SIZE - self->start;
+
+ dprintf("************** Multiple read\n");
+ memcpy(*buf, self->buffer + (((self->start + diff) % CACHE_BUFFER_SIZE) * DVD_VIDEO_LB_LEN),
+ boundary * DVD_VIDEO_LB_LEN);
+ memcpy(*buf + (boundary * DVD_VIDEO_LB_LEN), self->buffer,
+ (block_count-boundary) * DVD_VIDEO_LB_LEN);
+ self->read_point += block_count;
+ pthread_mutex_unlock(&self->cache_lock);
+
+ return (int)block_count;
+ }
+ } else {
+ /* Miss */
+
+ fprintf(stderr, "DVD read cache miss! (not bad but a performance hit) sector=%d\n", sector);
+ result = DVDReadBlocks( self->dvd_self->file, sector, block_count, *buf);
+ self->read_point = sector+block_count;
+ if(self->read_point > self->pos + self->size) {
+ /* Flush the cache as its not much use */
+ dprintf("Contents irrelevent... flushing\n");
+ self->size = 0;
+ self->start = 0;
+ self->pos = sector+block_count;
+ }
+ pthread_mutex_unlock(&self->cache_lock);
+ usleep(300);
+ return result;
+ }
+
+ /* Should never get here */
+ return 0;
+}
+
+dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) {
+ return DVDNAV_STATUS_OK;
+}
+
+#else
+
+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;
+ 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);
+ 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);
+}
+
+#ifdef DVDNAV_PROFILE
+//#ifdef ARCH_X86
+__inline__ unsigned long long int dvdnav_rdtsc()
+{
+ unsigned long long int x;
+ __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
+ return x;
+}
+//#endif
+#endif
+
+/* 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, result;
+#ifdef DVDNAV_PROFILE
+ struct timeval tv1, tv2, tv3;
+ unsigned long long p1, p2, p3;
+#endif
+
+ 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 = realloc(self->chunk[use].cache_buffer,
+ block_count * DVD_VIDEO_LB_LEN);
+ 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 = malloc((block_count > 500 ? block_count : 500) * DVD_VIDEO_LB_LEN);
+ 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->current = use;
+#ifdef DVDNAV_PROFILE
+ gettimeofday(&tv1, NULL);
+ p1 = dvdnav_rdtsc();
+#endif
+ result = DVDReadBlocks (self->dvd_self->file, sector, block_count, self->chunk[use].cache_buffer);
+#ifdef DVDNAV_PROFILE
+ p2 = dvdnav_rdtsc();
+ gettimeofday(&tv2, NULL);
+ timersub(&tv2, &tv1, &tv3);
+ dprintf("pre_cache DVD read %ld us, profile = %lld, block_count = %d\n",
+ tv3.tv_usec, p2-p1, block_count);
+#endif
+ self->chunk[use].cache_valid = 1;
+ } 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;
+
+ if(!self)
+ return 0;
+
+ pthread_mutex_lock(&self->lock);
+
+ 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 + 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 + block_count <= self->chunk[i].cache_start_sector + self->chunk[i].cache_block_count)
+ use = i;
+ }
+
+ if (use >= 0) {
+ self->chunk[use].usage_count++;
+ *buf = &self->chunk[use].cache_buffer[(sector - self->chunk[use].cache_start_sector) *
+ DVD_VIDEO_LB_LEN * block_count];
+ pthread_mutex_unlock(&self->lock);
+ return DVD_VIDEO_LB_LEN * block_count;
+ } else {
+ if (self->dvd_self->use_read_ahead)
+ dprintf("cache miss on sector %d\n", sector);
+ pthread_mutex_unlock(&self->lock);
+ return DVDReadBlocks(self->dvd_self->file, sector, block_count, *buf);
+ }
+}
+
+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;
+}
+
+#endif