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 }