Mercurial > libdvdread4.hg
comparison read_cache.c @ 0:427b7da5cbdb src
first split of dvdread; it's just a copy of dvdnav still to be cleaned
| author | nicodvb |
|---|---|
| date | Sun, 01 Jun 2008 08:39:07 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:427b7da5cbdb |
|---|---|
| 1 /* | |
| 2 * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> | |
| 3 * 2001-2004 the dvdnav project | |
| 4 * | |
| 5 * This file is part of libdvdnav, a DVD navigation library. | |
| 6 * | |
| 7 * libdvdnav is free software; you can redistribute it and/or modify | |
| 8 * it under the terms of the GNU General Public License as published by | |
| 9 * the Free Software Foundation; either version 2 of the License, or | |
| 10 * (at your option) any later version. | |
| 11 * | |
| 12 * libdvdnav is distributed in the hope that it will be useful, | |
| 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 15 * GNU General Public License for more details. | |
| 16 * | |
| 17 * You should have received a copy of the GNU General Public License | |
| 18 * along with this program; if not, write to the Free Software | |
| 19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA | |
| 20 * | |
| 21 * $Id$ | |
| 22 * | |
| 23 */ | |
| 24 /* | |
| 25 * There was a multithreaded read ahead cache in here for some time, but | |
| 26 * it had only been used for a short time. If you want to have a look at it, | |
| 27 * search the CVS attic. | |
| 28 */ | |
| 29 | |
| 30 #ifdef HAVE_CONFIG_H | |
| 31 #include "config.h" | |
| 32 #endif | |
| 33 | |
| 34 #include <inttypes.h> | |
| 35 #include <stdlib.h> | |
| 36 #include <limits.h> | |
| 37 #include <sys/time.h> | |
| 38 #include <time.h> | |
| 39 #include "dvd_types.h" | |
| 40 #include "nav_types.h" | |
| 41 #include "ifo_types.h" | |
| 42 #include "remap.h" | |
| 43 #include "vm/decoder.h" | |
| 44 #include "vm/vm.h" | |
| 45 #include "dvdnav.h" | |
| 46 #include "dvdnav_internal.h" | |
| 47 #include "read_cache.h" | |
| 48 | |
| 49 #define READ_CACHE_CHUNKS 10 | |
| 50 | |
| 51 /* all cache chunks must be memory aligned to allow use of raw devices */ | |
| 52 #define ALIGNMENT 2048 | |
| 53 | |
| 54 #define READ_AHEAD_SIZE_MIN 4 | |
| 55 #define READ_AHEAD_SIZE_MAX 512 | |
| 56 | |
| 57 typedef struct read_cache_chunk_s { | |
| 58 uint8_t *cache_buffer; | |
| 59 uint8_t *cache_buffer_base; /* used in malloc and free for alignment */ | |
| 60 int32_t cache_start_sector; /* -1 means cache invalid */ | |
| 61 int32_t cache_read_count; /* this many sectors are already read */ | |
| 62 size_t cache_block_count; /* this many sectors will go in this chunk */ | |
| 63 size_t cache_malloc_size; | |
| 64 int cache_valid; | |
| 65 int usage_count; /* counts how many buffers where issued from this chunk */ | |
| 66 } read_cache_chunk_t; | |
| 67 | |
| 68 struct read_cache_s { | |
| 69 read_cache_chunk_t chunk[READ_CACHE_CHUNKS]; | |
| 70 int current; | |
| 71 int freeing; /* is set to one when we are about to dispose the cache */ | |
| 72 uint32_t read_ahead_size; | |
| 73 int read_ahead_incr; | |
| 74 int last_sector; | |
| 75 pthread_mutex_t lock; | |
| 76 | |
| 77 /* Bit of strange cross-linking going on here :) -- Gotta love C :) */ | |
| 78 dvdnav_t *dvd_self; | |
| 79 }; | |
| 80 | |
| 81 /* | |
| 82 #define READ_CACHE_TRACE 0 | |
| 83 */ | |
| 84 | |
| 85 #ifdef __GNUC__ | |
| 86 # if READ_CACHE_TRACE | |
| 87 # define dprintf(fmt, args...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt, __func__ , ## args) | |
| 88 # else | |
| 89 # define dprintf(fmt, args...) /* Nowt */ | |
| 90 # endif | |
| 91 #else | |
| 92 # if READ_CACHE_TRACE | |
| 93 # define dprintf(fmt, ...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt, __func__ , __VA_ARGS__) | |
| 94 # else | |
| 95 #ifdef _MSC_VER | |
| 96 # define dprintf(fmt, str) /* Nowt */ | |
| 97 #else | |
| 98 # define dprintf(fmt, ...) /* Nowt */ | |
| 99 #endif /* _MSC_VER */ | |
| 100 # endif | |
| 101 #endif | |
| 102 | |
| 103 | |
| 104 read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) { | |
| 105 read_cache_t *self; | |
| 106 int i; | |
| 107 | |
| 108 self = (read_cache_t *)malloc(sizeof(read_cache_t)); | |
| 109 | |
| 110 if(self) { | |
| 111 self->current = 0; | |
| 112 self->freeing = 0; | |
| 113 self->dvd_self = dvd_self; | |
| 114 self->last_sector = 0; | |
| 115 self->read_ahead_size = READ_AHEAD_SIZE_MIN; | |
| 116 self->read_ahead_incr = 0; | |
| 117 pthread_mutex_init(&self->lock, NULL); | |
| 118 dvdnav_read_cache_clear(self); | |
| 119 for (i = 0; i < READ_CACHE_CHUNKS; i++) { | |
| 120 self->chunk[i].cache_buffer = NULL; | |
| 121 self->chunk[i].usage_count = 0; | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 return self; | |
| 126 } | |
| 127 | |
| 128 void dvdnav_read_cache_free(read_cache_t* self) { | |
| 129 dvdnav_t *tmp; | |
| 130 int i; | |
| 131 | |
| 132 pthread_mutex_lock(&self->lock); | |
| 133 self->freeing = 1; | |
| 134 for (i = 0; i < READ_CACHE_CHUNKS; i++) | |
| 135 if (self->chunk[i].cache_buffer && self->chunk[i].usage_count == 0) { | |
| 136 free(self->chunk[i].cache_buffer_base); | |
| 137 self->chunk[i].cache_buffer = NULL; | |
| 138 } | |
| 139 pthread_mutex_unlock(&self->lock); | |
| 140 | |
| 141 for (i = 0; i < READ_CACHE_CHUNKS; i++) | |
| 142 if (self->chunk[i].cache_buffer) return; | |
| 143 | |
| 144 /* all buffers returned, free everything */ | |
| 145 tmp = self->dvd_self; | |
| 146 pthread_mutex_destroy(&self->lock); | |
| 147 free(self); | |
| 148 free(tmp); | |
| 149 } | |
| 150 | |
| 151 /* This function MUST be called whenever self->file changes. */ | |
| 152 void dvdnav_read_cache_clear(read_cache_t *self) { | |
| 153 int i; | |
| 154 | |
| 155 if(!self) | |
| 156 return; | |
| 157 | |
| 158 pthread_mutex_lock(&self->lock); | |
| 159 for (i = 0; i < READ_CACHE_CHUNKS; i++) | |
| 160 self->chunk[i].cache_valid = 0; | |
| 161 pthread_mutex_unlock(&self->lock); | |
| 162 } | |
| 163 | |
| 164 /* This function is called just after reading the NAV packet. */ | |
| 165 void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count) { | |
| 166 int i, use; | |
| 167 | |
| 168 if(!self) | |
| 169 return; | |
| 170 | |
| 171 if(!self->dvd_self->use_read_ahead) | |
| 172 return; | |
| 173 | |
| 174 pthread_mutex_lock(&self->lock); | |
| 175 | |
| 176 /* find a free cache chunk that best fits the required size */ | |
| 177 use = -1; | |
| 178 for (i = 0; i < READ_CACHE_CHUNKS; i++) | |
| 179 if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer && | |
| 180 self->chunk[i].cache_malloc_size >= block_count && | |
| 181 (use == -1 || self->chunk[use].cache_malloc_size > self->chunk[i].cache_malloc_size)) | |
| 182 use = i; | |
| 183 | |
| 184 if (use == -1) { | |
| 185 /* we haven't found a cache chunk, so we try to reallocate an existing one */ | |
| 186 for (i = 0; i < READ_CACHE_CHUNKS; i++) | |
| 187 if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer && | |
| 188 (use == -1 || self->chunk[use].cache_malloc_size < self->chunk[i].cache_malloc_size)) | |
| 189 use = i; | |
| 190 if (use >= 0) { | |
| 191 self->chunk[use].cache_buffer_base = realloc(self->chunk[use].cache_buffer_base, | |
| 192 block_count * DVD_VIDEO_LB_LEN + ALIGNMENT); | |
| 193 self->chunk[use].cache_buffer = | |
| 194 (uint8_t *)(((uintptr_t)self->chunk[use].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT); | |
| 195 dprintf("pre_cache DVD read realloc happened\n"); | |
| 196 self->chunk[use].cache_malloc_size = block_count; | |
| 197 } else { | |
| 198 /* we still haven't found a cache chunk, let's allocate a new one */ | |
| 199 for (i = 0; i < READ_CACHE_CHUNKS; i++) | |
| 200 if (!self->chunk[i].cache_buffer) { | |
| 201 use = i; | |
| 202 break; | |
| 203 } | |
| 204 if (use >= 0) { | |
| 205 /* We start with a sensible figure for the first malloc of 500 blocks. | |
| 206 * Some DVDs I have seen venture to 450 blocks. | |
| 207 * This is so that fewer realloc's happen if at all. | |
| 208 */ | |
| 209 self->chunk[i].cache_buffer_base = | |
| 210 malloc((block_count > 500 ? block_count : 500) * DVD_VIDEO_LB_LEN + ALIGNMENT); | |
| 211 self->chunk[i].cache_buffer = | |
| 212 (uint8_t *)(((uintptr_t)self->chunk[i].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT); | |
| 213 self->chunk[i].cache_malloc_size = block_count > 500 ? block_count : 500; | |
| 214 dprintf("pre_cache DVD read malloc %d blocks\n", | |
| 215 (block_count > 500 ? block_count : 500 )); | |
| 216 } | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 if (use >= 0) { | |
| 221 self->chunk[use].cache_start_sector = sector; | |
| 222 self->chunk[use].cache_block_count = block_count; | |
| 223 self->chunk[use].cache_read_count = 0; | |
| 224 self->chunk[use].cache_valid = 1; | |
| 225 self->current = use; | |
| 226 } else { | |
| 227 dprintf("pre_caching was impossible, no cache chunk available\n"); | |
| 228 } | |
| 229 pthread_mutex_unlock(&self->lock); | |
| 230 } | |
| 231 | |
| 232 int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf) { | |
| 233 int i, use; | |
| 234 int start; | |
| 235 int size; | |
| 236 int incr; | |
| 237 uint8_t *read_ahead_buf; | |
| 238 int32_t res; | |
| 239 | |
| 240 if(!self) | |
| 241 return 0; | |
| 242 | |
| 243 use = -1; | |
| 244 | |
| 245 if(self->dvd_self->use_read_ahead) { | |
| 246 /* first check, if sector is in current chunk */ | |
| 247 read_cache_chunk_t cur = self->chunk[self->current]; | |
| 248 if (cur.cache_valid && sector >= cur.cache_start_sector && | |
| 249 sector <= (cur.cache_start_sector + cur.cache_read_count) && | |
| 250 sector + block_count <= cur.cache_start_sector + cur.cache_block_count) | |
| 251 use = self->current; | |
| 252 else | |
| 253 for (i = 0; i < READ_CACHE_CHUNKS; i++) | |
| 254 if (self->chunk[i].cache_valid && | |
| 255 sector >= self->chunk[i].cache_start_sector && | |
| 256 sector <= (self->chunk[i].cache_start_sector + self->chunk[i].cache_read_count) && | |
| 257 sector + block_count <= self->chunk[i].cache_start_sector + self->chunk[i].cache_block_count) | |
| 258 use = i; | |
| 259 } | |
| 260 | |
| 261 if (use >= 0) { | |
| 262 read_cache_chunk_t *chunk; | |
| 263 | |
| 264 /* Increment read-ahead size if sector follows the last sector */ | |
| 265 if (sector == (self->last_sector + 1)) { | |
| 266 if (self->read_ahead_incr < READ_AHEAD_SIZE_MAX) | |
| 267 self->read_ahead_incr++; | |
| 268 } else { | |
| 269 self->read_ahead_size = READ_AHEAD_SIZE_MIN; | |
| 270 self->read_ahead_incr = 0; | |
| 271 } | |
| 272 self->last_sector = sector; | |
| 273 | |
| 274 /* The following resources need to be protected by a mutex : | |
| 275 * self->chunk[*].cache_buffer | |
| 276 * self->chunk[*].cache_malloc_size | |
| 277 * self->chunk[*].usage_count | |
| 278 */ | |
| 279 pthread_mutex_lock(&self->lock); | |
| 280 chunk = &self->chunk[use]; | |
| 281 read_ahead_buf = chunk->cache_buffer + chunk->cache_read_count * DVD_VIDEO_LB_LEN; | |
| 282 *buf = chunk->cache_buffer + (sector - chunk->cache_start_sector) * DVD_VIDEO_LB_LEN; | |
| 283 chunk->usage_count++; | |
| 284 pthread_mutex_unlock(&self->lock); | |
| 285 | |
| 286 dprintf("libdvdnav: sector=%d, start_sector=%d, last_sector=%d\n", sector, chunk->cache_start_sector, chunk->cache_start_sector + chunk->cache_block_count); | |
| 287 | |
| 288 /* read_ahead_size */ | |
| 289 incr = self->read_ahead_incr >> 1; | |
| 290 if ((self->read_ahead_size + incr) > READ_AHEAD_SIZE_MAX) { | |
| 291 self->read_ahead_size = READ_AHEAD_SIZE_MAX; | |
| 292 } else { | |
| 293 self->read_ahead_size += incr; | |
| 294 } | |
| 295 | |
| 296 /* real read size */ | |
| 297 start = chunk->cache_start_sector + chunk->cache_read_count; | |
| 298 if (chunk->cache_read_count + self->read_ahead_size > chunk->cache_block_count) { | |
| 299 size = chunk->cache_block_count - chunk->cache_read_count; | |
| 300 } else { | |
| 301 size = self->read_ahead_size; | |
| 302 /* ensure that the sector we want will be read */ | |
| 303 if (sector >= chunk->cache_start_sector + chunk->cache_read_count + size) | |
| 304 size = sector - chunk->cache_start_sector - chunk->cache_read_count; | |
| 305 } | |
| 306 dprintf("libdvdnav: read_ahead_size=%d, size=%d\n", self->read_ahead_size, size); | |
| 307 | |
| 308 if (size) | |
| 309 chunk->cache_read_count += DVDReadBlocks(self->dvd_self->file, | |
| 310 start, | |
| 311 size, | |
| 312 read_ahead_buf); | |
| 313 | |
| 314 res = DVD_VIDEO_LB_LEN * block_count; | |
| 315 | |
| 316 } else { | |
| 317 | |
| 318 if (self->dvd_self->use_read_ahead) | |
| 319 dprintf("cache miss on sector %d\n", sector); | |
| 320 | |
| 321 res = DVDReadBlocks(self->dvd_self->file, | |
| 322 sector, | |
| 323 block_count, | |
| 324 *buf) * DVD_VIDEO_LB_LEN; | |
| 325 } | |
| 326 | |
| 327 return res; | |
| 328 | |
| 329 } | |
| 330 | |
| 331 dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) { | |
| 332 read_cache_t *cache; | |
| 333 int i; | |
| 334 | |
| 335 if (!self) | |
| 336 return DVDNAV_STATUS_ERR; | |
| 337 | |
| 338 cache = self->cache; | |
| 339 if (!cache) | |
| 340 return DVDNAV_STATUS_ERR; | |
| 341 | |
| 342 pthread_mutex_lock(&cache->lock); | |
| 343 for (i = 0; i < READ_CACHE_CHUNKS; i++) { | |
| 344 if (cache->chunk[i].cache_buffer && buf >= cache->chunk[i].cache_buffer && | |
| 345 buf < cache->chunk[i].cache_buffer + cache->chunk[i].cache_malloc_size * DVD_VIDEO_LB_LEN) { | |
| 346 cache->chunk[i].usage_count--; | |
| 347 } | |
| 348 } | |
| 349 pthread_mutex_unlock(&cache->lock); | |
| 350 | |
| 351 if (cache->freeing) | |
| 352 /* when we want to dispose the cache, try freeing it now */ | |
| 353 dvdnav_read_cache_free(cache); | |
| 354 | |
| 355 return DVDNAV_STATUS_OK; | |
| 356 } |
