MythTV  0.26-pre
read_cache.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
00003  *               2001-2004 the dvdnav project
00004  *
00005  * This file is part of libdvdnav, a DVD navigation library.
00006  *
00007  * libdvdnav is free software; you can redistribute it and/or modify
00008  * it under the terms of the GNU General Public License as published by
00009  * the Free Software Foundation; either version 2 of the License, or
00010  * (at your option) any later version.
00011  *
00012  * libdvdnav is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License along
00018  * with libdvdnav; if not, write to the Free Software Foundation, Inc.,
00019  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
00020  */
00021 /*
00022  * There was a multithreaded read ahead cache in here for some time, but
00023  * it had only been used for a short time. If you want to have a look at it,
00024  * search the CVS attic.
00025  */
00026 
00027 #ifdef HAVE_CONFIG_H
00028 #include "config.h"
00029 #endif
00030 
00031 #include <inttypes.h>
00032 #include <stdlib.h>
00033 #include <limits.h>
00034 #include <sys/time.h>
00035 #include <time.h>
00036 #include "dvdnav/dvdnav.h"
00037 #include <dvdread/nav_types.h>
00038 #include <dvdread/ifo_types.h>
00039 #include "remap.h"
00040 #include "vm/decoder.h"
00041 #include "vm/vm.h"
00042 #include "dvdnav_internal.h"
00043 #include "read_cache.h"
00044 
00045 #define READ_CACHE_CHUNKS 10
00046 
00047 /* all cache chunks must be memory aligned to allow use of raw devices */
00048 #define ALIGNMENT 2048
00049 
00050 #define READ_AHEAD_SIZE_MIN 4
00051 #define READ_AHEAD_SIZE_MAX 512
00052 
00053 typedef struct read_cache_chunk_s {
00054   uint8_t     *cache_buffer;
00055   uint8_t     *cache_buffer_base;  /* used in malloc and free for alignment */
00056   int32_t      cache_start_sector; /* -1 means cache invalid */
00057   int32_t      cache_read_count;   /* this many sectors are already read */
00058   size_t       cache_block_count;  /* this many sectors will go in this chunk */
00059   size_t       cache_malloc_size;
00060   int          cache_valid;
00061   int          usage_count;  /* counts how many buffers where issued from this chunk */
00062 } read_cache_chunk_t;
00063 
00064 struct read_cache_s {
00065   read_cache_chunk_t  chunk[READ_CACHE_CHUNKS];
00066   int                 current;
00067   int                 freeing;  /* is set to one when we are about to dispose the cache */
00068   uint32_t            read_ahead_size;
00069   int                 read_ahead_incr;
00070   int                 last_sector;
00071   pthread_mutex_t     lock;
00072 
00073   /* Bit of strange cross-linking going on here :) -- Gotta love C :) */
00074   dvdnav_t           *dvd_self;
00075 };
00076 
00077 /*
00078 #define READ_CACHE_TRACE 0
00079 */
00080 
00081 #ifdef __GNUC__
00082 # if READ_CACHE_TRACE
00083 #  define dprintf(fmt, args...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt,  __func__ , ## args)
00084 # else
00085 #  define dprintf(fmt, args...) /* Nowt */
00086 # endif
00087 #else
00088 # if READ_CACHE_TRACE
00089 #  define dprintf(fmt, ...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt,  __func__ , __VA_ARGS__)
00090 # else
00091 #ifdef _MSC_VER
00092 #  define dprintf(fmt, str) /* Nowt */
00093 #else
00094 #  define dprintf(fmt, ...) /* Nowt */
00095 #endif /* _MSC_VER */
00096 # endif
00097 #endif
00098 
00099 
00100 read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) {
00101   read_cache_t *self;
00102   int i;
00103 
00104   self = (read_cache_t *)malloc(sizeof(read_cache_t));
00105 
00106   if(self) {
00107     self->current = 0;
00108     self->freeing = 0;
00109     self->dvd_self = dvd_self;
00110     self->last_sector = 0;
00111     self->read_ahead_size = READ_AHEAD_SIZE_MIN;
00112     self->read_ahead_incr = 0;
00113     pthread_mutex_init(&self->lock, NULL);
00114     dvdnav_read_cache_clear(self);
00115     for (i = 0; i < READ_CACHE_CHUNKS; i++) {
00116       self->chunk[i].cache_buffer = NULL;
00117       self->chunk[i].usage_count = 0;
00118     }
00119   }
00120 
00121   return self;
00122 }
00123 
00124 void dvdnav_read_cache_free(read_cache_t* self) {
00125   dvdnav_t *tmp;
00126   int i;
00127 
00128   pthread_mutex_lock(&self->lock);
00129   self->freeing = 1;
00130   for (i = 0; i < READ_CACHE_CHUNKS; i++)
00131     if (self->chunk[i].cache_buffer && self->chunk[i].usage_count == 0) {
00132       free(self->chunk[i].cache_buffer_base);
00133       self->chunk[i].cache_buffer = NULL;
00134     }
00135   pthread_mutex_unlock(&self->lock);
00136 
00137   for (i = 0; i < READ_CACHE_CHUNKS; i++)
00138     if (self->chunk[i].cache_buffer) return;
00139 
00140   /* all buffers returned, free everything */
00141   tmp = self->dvd_self;
00142   pthread_mutex_destroy(&self->lock);
00143   free(self);
00144   free(tmp);
00145 }
00146 
00147 /* This function MUST be called whenever self->file changes. */
00148 void dvdnav_read_cache_clear(read_cache_t *self) {
00149   int i;
00150 
00151   if(!self)
00152    return;
00153 
00154   pthread_mutex_lock(&self->lock);
00155   for (i = 0; i < READ_CACHE_CHUNKS; i++)
00156     self->chunk[i].cache_valid = 0;
00157   pthread_mutex_unlock(&self->lock);
00158 }
00159 
00160 /* This function is called just after reading the NAV packet. */
00161 void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count) {
00162   int i, use;
00163 
00164   if(!self)
00165     return;
00166 
00167   if(!self->dvd_self->use_read_ahead)
00168     return;
00169 
00170   pthread_mutex_lock(&self->lock);
00171 
00172   /* find a free cache chunk that best fits the required size */
00173   use = -1;
00174   for (i = 0; i < READ_CACHE_CHUNKS; i++)
00175     if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
00176         self->chunk[i].cache_malloc_size >= block_count &&
00177         (use == -1 || self->chunk[use].cache_malloc_size > self->chunk[i].cache_malloc_size))
00178       use = i;
00179 
00180   if (use == -1) {
00181     /* we haven't found a cache chunk, so we try to reallocate an existing one */
00182     for (i = 0; i < READ_CACHE_CHUNKS; i++)
00183       if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
00184           (use == -1 || self->chunk[use].cache_malloc_size < self->chunk[i].cache_malloc_size))
00185         use = i;
00186     if (use >= 0) {
00187       self->chunk[use].cache_buffer_base = realloc(self->chunk[use].cache_buffer_base,
00188         block_count * DVD_VIDEO_LB_LEN + ALIGNMENT);
00189       self->chunk[use].cache_buffer =
00190         (uint8_t *)(((uintptr_t)self->chunk[use].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
00191       dprintf("pre_cache DVD read realloc happened\n");
00192       self->chunk[use].cache_malloc_size = block_count;
00193     } else {
00194       /* we still haven't found a cache chunk, let's allocate a new one */
00195       for (i = 0; i < READ_CACHE_CHUNKS; i++)
00196         if (!self->chunk[i].cache_buffer) {
00197           use = i;
00198           break;
00199         }
00200       if (use >= 0) {
00201         /* We start with a sensible figure for the first malloc of 500 blocks.
00202          * Some DVDs I have seen venture to 450 blocks.
00203          * This is so that fewer realloc's happen if at all.
00204          */
00205         self->chunk[i].cache_buffer_base =
00206           malloc((block_count > 500 ? block_count : 500) * DVD_VIDEO_LB_LEN + ALIGNMENT);
00207         self->chunk[i].cache_buffer =
00208           (uint8_t *)(((uintptr_t)self->chunk[i].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
00209         self->chunk[i].cache_malloc_size = block_count > 500 ? block_count : 500;
00210         dprintf("pre_cache DVD read malloc %d blocks\n",
00211           (block_count > 500 ? block_count : 500 ));
00212       }
00213     }
00214   }
00215 
00216   if (use >= 0) {
00217     self->chunk[use].cache_start_sector = sector;
00218     self->chunk[use].cache_block_count = block_count;
00219     self->chunk[use].cache_read_count = 0;
00220     self->chunk[use].cache_valid = 1;
00221     self->current = use;
00222   } else {
00223     dprintf("pre_caching was impossible, no cache chunk available\n");
00224   }
00225   pthread_mutex_unlock(&self->lock);
00226 }
00227 
00228 int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf) {
00229   int i, use;
00230   int start;
00231   int size;
00232   int incr;
00233   uint8_t *read_ahead_buf;
00234   int32_t res;
00235 
00236   if(!self)
00237     return 0;
00238 
00239   use = -1;
00240 
00241   if(self->dvd_self->use_read_ahead) {
00242     /* first check, if sector is in current chunk */
00243     read_cache_chunk_t cur = self->chunk[self->current];
00244     if (cur.cache_valid && sector >= cur.cache_start_sector &&
00245         sector <= (cur.cache_start_sector + cur.cache_read_count) &&
00246         sector + block_count <= cur.cache_start_sector + cur.cache_block_count)
00247       use = self->current;
00248     else
00249       for (i = 0; i < READ_CACHE_CHUNKS; i++)
00250         if (self->chunk[i].cache_valid &&
00251             sector >= self->chunk[i].cache_start_sector &&
00252             sector <= (self->chunk[i].cache_start_sector + self->chunk[i].cache_read_count) &&
00253             sector + block_count <= self->chunk[i].cache_start_sector + self->chunk[i].cache_block_count)
00254             use = i;
00255   }
00256 
00257   if (use >= 0) {
00258     read_cache_chunk_t *chunk;
00259 
00260     /* Increment read-ahead size if sector follows the last sector */
00261     if (sector == (self->last_sector + 1)) {
00262       if (self->read_ahead_incr < READ_AHEAD_SIZE_MAX)
00263         self->read_ahead_incr++;
00264     } else {
00265       self->read_ahead_size = READ_AHEAD_SIZE_MIN;
00266       self->read_ahead_incr = 0;
00267     }
00268     self->last_sector = sector;
00269 
00270     /* The following resources need to be protected by a mutex :
00271      *   self->chunk[*].cache_buffer
00272      *   self->chunk[*].cache_malloc_size
00273      *   self->chunk[*].usage_count
00274      */
00275     pthread_mutex_lock(&self->lock);
00276     chunk = &self->chunk[use];
00277     read_ahead_buf = chunk->cache_buffer + chunk->cache_read_count * DVD_VIDEO_LB_LEN;
00278     *buf = chunk->cache_buffer + (sector - chunk->cache_start_sector) * DVD_VIDEO_LB_LEN;
00279     chunk->usage_count++;
00280     pthread_mutex_unlock(&self->lock);
00281 
00282     dprintf("libdvdnav: sector=%d, start_sector=%d, last_sector=%d\n", sector, chunk->cache_start_sector, chunk->cache_start_sector + chunk->cache_block_count);
00283 
00284     /* read_ahead_size */
00285     incr = self->read_ahead_incr >> 1;
00286     if ((self->read_ahead_size + incr) > READ_AHEAD_SIZE_MAX) {
00287       self->read_ahead_size = READ_AHEAD_SIZE_MAX;
00288     } else {
00289       self->read_ahead_size += incr;
00290     }
00291 
00292     /* real read size */
00293     start = chunk->cache_start_sector + chunk->cache_read_count;
00294     if (chunk->cache_read_count + self->read_ahead_size > chunk->cache_block_count) {
00295       size = chunk->cache_block_count - chunk->cache_read_count;
00296     } else {
00297       size = self->read_ahead_size;
00298       /* ensure that the sector we want will be read */
00299       if (sector >= chunk->cache_start_sector + chunk->cache_read_count + size)
00300         size = sector - chunk->cache_start_sector - chunk->cache_read_count;
00301     }
00302     dprintf("libdvdnav: read_ahead_size=%d, size=%d\n", self->read_ahead_size, size);
00303 
00304     if (size)
00305       chunk->cache_read_count += DVDReadBlocks(self->dvd_self->file,
00306                                                start,
00307                                                size,
00308                                                read_ahead_buf);
00309 
00310     res = DVD_VIDEO_LB_LEN * block_count;
00311 
00312   } else {
00313 
00314     if (self->dvd_self->use_read_ahead)
00315       dprintf("cache miss on sector %d\n", sector);
00316 
00317     res = DVDReadBlocks(self->dvd_self->file,
00318                         sector,
00319                         block_count,
00320                         *buf) * DVD_VIDEO_LB_LEN;
00321   }
00322 
00323   return res;
00324 
00325 }
00326 
00327 dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) {
00328   read_cache_t *cache;
00329   int i;
00330 
00331   if (!self)
00332     return DVDNAV_STATUS_ERR;
00333 
00334   cache = self->cache;
00335   if (!cache)
00336     return DVDNAV_STATUS_ERR;
00337 
00338   pthread_mutex_lock(&cache->lock);
00339   for (i = 0; i < READ_CACHE_CHUNKS; i++) {
00340     if (cache->chunk[i].cache_buffer && buf >= cache->chunk[i].cache_buffer &&
00341         buf < cache->chunk[i].cache_buffer + cache->chunk[i].cache_malloc_size * DVD_VIDEO_LB_LEN) {
00342       cache->chunk[i].usage_count--;
00343     }
00344   }
00345   pthread_mutex_unlock(&cache->lock);
00346 
00347   if (cache->freeing)
00348     /* when we want to dispose the cache, try freeing it now */
00349     dvdnav_read_cache_free(cache);
00350 
00351   return DVDNAV_STATUS_OK;
00352 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends