Mercurial > libdvdnav.hg
changeset 60:30995ad032cf src
use new memcopy'less read ahead cache
author | mroi |
---|---|
date | Fri, 12 Jul 2002 15:46:44 +0000 |
parents | 6b90f8ffd8c0 |
children | 6b7520caf9a1 |
files | decoder.h dvdnav.c dvdnav.h read_cache.c read_cache.h |
diffstat | 5 files changed, 249 insertions(+), 112 deletions(-) [+] |
line wrap: on
line diff
--- a/decoder.h Fri Jul 12 14:02:52 2002 +0000 +++ b/decoder.h Fri Jul 12 15:46:44 2002 +0000 @@ -31,7 +31,7 @@ #include <dvdread/ifo_types.h> /* vm_cmd_t */ /* Uncomment for tracing */ -#define TRACE +/* #define TRACE */ typedef enum { LinkNoLink = 0,
--- a/dvdnav.c Fri Jul 12 14:02:52 2002 +0000 +++ b/dvdnav.c Fri Jul 12 15:46:44 2002 +0000 @@ -230,13 +230,6 @@ fprintf(stderr,"dvdnav:close:called\n"); #endif - /* Stop caching */ - - if(this->cache) { - dvdnav_read_cache_free(this->cache); - this->cache = NULL; - } - if (this->file) { DVDCloseFile(this->file); #ifdef LOG_DEBUG @@ -257,8 +250,13 @@ this->file = NULL; } pthread_mutex_destroy(&this->vm_lock); - /* Finally free the entire structure */ - free(this); + + /* We leave the final freeing of the entire structure to the cache, + * because we don't know, if there are still buffers out in the wild, + * that must return first. */ + if(this->cache) { + dvdnav_read_cache_free(this->cache); + } else free(this); return S_OK; } @@ -482,16 +480,33 @@ return 1; } + /* This is the main get_next_block function which actually gets the media stream video and audio etc. * The use of this function is optional, with the application programmer * free to implement their own version of this function * FIXME: Make the function calls from here public API calls. */ + dvdnav_status_t dvdnav_get_next_block(dvdnav_t *this, unsigned char *buf, int *event, int *len) { + unsigned char *block; + dvdnav_status_t status; + + block = buf; + status = dvdnav_get_next_cache_block(this, &block, event, len); + if (block != buf) { + /* we received a block from the cache, copy it, so we can give it back */ + memcpy(buf, block, DVD_VIDEO_LB_LEN); + dvdnav_free_cache_block(this, block); + } + return status; +} + +dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *this, unsigned char **buf, + int *event, int *len) { dvd_state_t *state; int result; - if(!this || !event || !len || !buf) { + if(!this || !event || !len || !buf || !*buf) { printerr("Passed a NULL pointer"); return S_ERR; } @@ -523,7 +538,7 @@ (*event) = DVDNAV_STILL_FRAME; (*len) = sizeof(dvdnav_still_event_t); - memcpy(buf, &(still_event), sizeof(dvdnav_still_event_t)); + memcpy(*buf, &(still_event), sizeof(dvdnav_still_event_t)); pthread_mutex_unlock(&this->vm_lock); return S_OK; @@ -553,7 +568,7 @@ fprintf(stderr,"libdvdnav:SPU_CLUT_CHANGE\n"); #endif (*len) = sizeof(dvdnav_still_event_t); - memcpy(buf, &(state->pgc->palette), 16 * sizeof(uint32_t)); + memcpy(*buf, &(state->pgc->palette), 16 * sizeof(uint32_t)); this->spu_clut_changed = 0; #ifdef LOG_DEBUG fprintf(stderr,"libdvdnav:SPU_CLUT_CHANGE returning S_OK\n"); @@ -572,7 +587,7 @@ stream_change.physical_wide = vm_get_subp_active_stream(this->vm, 0); stream_change.physical_letterbox = vm_get_subp_active_stream(this->vm, 1); stream_change.physical_pan_scan = vm_get_subp_active_stream(this->vm, 2); - memcpy(buf, &(stream_change), sizeof( dvdnav_spu_stream_change_event_t)); + memcpy(*buf, &(stream_change), sizeof( dvdnav_spu_stream_change_event_t)); this->position_current.spu_channel = this->position_next.spu_channel; #ifdef LOG_DEBUG fprintf(stderr,"libdvdnav:SPU_STREAM_CHANGE stream_id_wide=%d\n",stream_change.physical_wide); @@ -598,7 +613,7 @@ #endif (*len) = sizeof(dvdnav_audio_stream_change_event_t); stream_change.physical= vm_get_audio_active_stream( this->vm ); - memcpy(buf, &(stream_change), sizeof( dvdnav_audio_stream_change_event_t)); + memcpy(*buf, &(stream_change), sizeof( dvdnav_audio_stream_change_event_t)); this->position_current.audio_channel = this->position_next.audio_channel; #ifdef LOG_DEBUG fprintf(stderr,"libdvdnav:AUDIO_STREAM_CHANGE stream_id=%d returning S_OK\n",stream_change.physical); @@ -619,7 +634,7 @@ (*event) = DVDNAV_HIGHLIGHT; (*len) = sizeof(hevent); - memcpy(buf, &(hevent), sizeof(hevent)); + memcpy(*buf, &(hevent), sizeof(hevent)); pthread_mutex_unlock(&this->vm_lock); return S_OK; } @@ -677,7 +692,7 @@ /* File opened successfully so return a VTS change event */ (*event) = DVDNAV_VTS_CHANGE; - memcpy(buf, &(vts_event), sizeof(vts_event)); + memcpy(*buf, &(vts_event), sizeof(vts_event)); (*len) = sizeof(vts_event); /* On a VTS change, we want to disable any highlights which @@ -748,7 +763,7 @@ still_event.length = this->position_current.still; (*event) = DVDNAV_STILL_FRAME; (*len) = sizeof(dvdnav_still_event_t); - memcpy(buf, &(still_event), sizeof(dvdnav_still_event_t)); + memcpy(*buf, &(still_event), sizeof(dvdnav_still_event_t)); pthread_mutex_unlock(&this->vm_lock); return S_OK; } @@ -774,7 +789,7 @@ } /* Decode nav into pci and dsi. */ /* Then get next VOBU info. */ - if(dvdnav_decode_packet(this, buf, &this->dsi, &this->pci) == 0) { + if(dvdnav_decode_packet(this, *buf, &this->dsi, &this->pci) == 0) { printerr("Expected NAV packet but none found."); pthread_mutex_unlock(&this->vm_lock); return S_ERR; @@ -963,6 +978,9 @@ /* * $Log$ + * Revision 1.27 2002/07/12 15:46:44 mroi + * use new memcopy'less read ahead cache + * * Revision 1.26 2002/07/06 16:24:54 mroi * * fix debug messages * * send spu stream change event only, when there are new streams
--- a/dvdnav.h Fri Jul 12 14:02:52 2002 +0000 +++ b/dvdnav.h Fri Jul 12 15:46:44 2002 +0000 @@ -208,6 +208,33 @@ int *event, int *len); /** + * This basically does the same as dvdnav_get_next_block. The only difference is + * that it avoids a memcopy, when the requested block was found in the cache. + * I such a case (cache hit) this function will return a different pointer than + * the one handed in, pointing directly into the relevant block in the cache. + * Those pointer must _never_ be freed but instead returned to the library via + + dvdnav_free_cache_block. + * + * \param self Pointer to dvdnav_t associated with this operation. + * \param buf Buffer (at least 2048 octets) to fill with next block/event structure. + * A different buffer might be returned, if the block was found in the internal cache. + * \param event Pointer to int to get event type. + * \param len Pointer to int to get the number of octets written into buf. + */ +dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *self, unsigned char **buf, + int *event, int *len); + +/** + * All buffers which came from the internal cache (when dvdnav_get_next_cache_block + * returned a buffer different from the one handed in) have to be freed with this + * function. Although handing in other buffers not from the cache doesn't cause any harm. + * + * \param self Pointer to dvdnav_t associated with this operation. + * \param buf Buffer received from internal cache. + */ +dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf); + +/** * Get video aspect code. * * \param self Pointer to dvdnav_t associated with this operation.
--- a/read_cache.c Fri Jul 12 14:02:52 2002 +0000 +++ b/read_cache.c Fri Jul 12 15:46:44 2002 +0000 @@ -36,8 +36,7 @@ */ /* Read-ahead cache structure. */ -#if 0 -/* #if _MULTITHREAD_ */ +#if _MULTITHREAD_ /* For the multithreaded cache, the cache is a ring buffer + writing * thread that continuously reads data into the buffer until it is @@ -67,16 +66,25 @@ }; #else -struct read_cache_s { - /* Read-ahead cache. */ - uint8_t *cache_buffer; + +#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 */ /* Bit of strange cross-linking going on here :) -- Gotta love C :) */ - dvdnav_t *dvd_self; + dvdnav_t *dvd_self; }; #endif @@ -88,8 +96,7 @@ #define dprintf(fmt, args...) /* Nowt */ #endif -#if 0 -/* #if _MULTITHREAD_ */ +#if _MULTITHREAD_ void * read_cache_read_thread (void * this_gen) { int cont = 1; @@ -190,6 +197,8 @@ } void dvdnav_read_cache_free(read_cache_t* self) { + dvdnav_t *tmp; + pthread_mutex_lock(&self->cache_lock); if(self->buffer) { @@ -203,8 +212,12 @@ 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. */ @@ -250,7 +263,7 @@ } /* 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 dvdnav_read_cache_block( read_cache_t *self, int sector, size_t block_count, uint8_t **buf) { int result, diff; if(!self) @@ -272,7 +285,7 @@ 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), + 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); @@ -282,9 +295,9 @@ 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), + 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, + 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); @@ -295,7 +308,7 @@ /* 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); + 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 */ @@ -313,43 +326,64 @@ 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 *me; + read_cache_t *self; + int i; - me = (read_cache_t*)malloc(sizeof(struct read_cache_s)); + self = (read_cache_t *)malloc(sizeof(read_cache_t)); - if(me) { - me->dvd_self = dvd_self; - - dvdnav_read_cache_clear(me); - me->cache_buffer = NULL; + if(self) { + self->current = 0; + self->freeing = 0; + self->dvd_self = dvd_self; + dvdnav_read_cache_clear(self); + for (i = 0; i < READ_CACHE_CHUNKS; i++) { + self->chunk[i].cache_buffer = NULL; + self->chunk[i].cache_block_count = 0; + self->chunk[i].usage_count = 0; + } } - /* this->cache_start_sector = -1; - this->cache_block_count = 0; - this->cache_valid = 0; */ - - return me; + return self; } void dvdnav_read_cache_free(read_cache_t* self) { - if(self->cache_buffer) { - free(self->cache_buffer); - self->cache_buffer = NULL; - } + dvdnav_t *tmp; + int i; + + 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; + } + + for (i = 0; i < READ_CACHE_CHUNKS; i++) + if (self->chunk[i].cache_buffer) return; + /* all buffers returned, free everything */ + tmp = self->dvd_self; 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; - - self->cache_start_sector = -1; - self->cache_valid = 0; + + for (i = 0; i < READ_CACHE_CHUNKS; i++) { + self->chunk[i].cache_start_sector = -1; + self->chunk[i].cache_valid = 0; + } } #ifdef DVDNAV_PROFILE @@ -365,75 +399,130 @@ /* 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 result; + int i, use, result; #ifdef DVDNAV_PROFILE struct timeval tv1, tv2, tv3; unsigned long long p1, p2, p3; #endif if(!self) - return; + return; - if(!self->dvd_self->use_read_ahead) { - self->cache_valid = 0; - self->cache_start_sector = -1; + if(!self->dvd_self->use_read_ahead) return; - } - /* 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. - */ - if (self->cache_buffer) { - if(block_count > self->cache_malloc_size) { - self->cache_buffer = realloc(self->cache_buffer, block_count * DVD_VIDEO_LB_LEN); - dprintf("libdvdnav:read_cache:pre_cache DVD read realloc happened\n"); - self->cache_malloc_size = block_count; - } - } else { - self->cache_buffer = malloc((block_count > 500 ? block_count : 500 )* DVD_VIDEO_LB_LEN); - self->cache_malloc_size = (block_count > 500 ? block_count : 500 ); - dprintf("libdvdnav:read_cache:pre_cache DVD read malloc %d\n", (block_count > 500 ? block_count : 500 )); - } - self->cache_start_sector = sector; - self->cache_block_count = block_count; -#ifdef DVDNAV_PROFILE - gettimeofday(&tv1, NULL); - p1 = dvdnav_rdtsc(); -#endif - result = DVDReadBlocks( self->dvd_self->file, sector, block_count, self->cache_buffer); -#ifdef DVDNAV_PROFILE - p2 = dvdnav_rdtsc(); - gettimeofday(&tv2, NULL); - timersub(&tv2, &tv1, &tv3); - dprintf("libdvdnav:read_cache:pre_cache DVD read %ld us, profile = %lld, block_count = %d\n", tv3.tv_usec, p2-p1, block_count); -#endif - self->cache_valid = 1; -} -/* 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; - - if(!self) - return 0; - - if(self->cache_valid && self->dvd_self->use_read_ahead) { - if (self->cache_start_sector != -1 ) { - if ((sector >= self->cache_start_sector) && - (sector < self->cache_start_sector + self->cache_block_count)) { - memcpy(buf, self->cache_buffer + ((off_t)((off_t)sector - (off_t)self->cache_start_sector) * DVD_VIDEO_LB_LEN), DVD_VIDEO_LB_LEN); - return DVD_VIDEO_LB_LEN; + /* 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_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 )); } } } - /* Disable dprintf if read cache is disabled. */ - //if(self->dvd_self->use_read_ahead) { - // dprintf("DVD read cache miss! sector=%d, start=%d, end=%d\n", - // sector, self->cache_start_sector, self->cache_block_count + self->cache_start_sector); - //} - result = DVDReadBlocks( self->dvd_self->file, sector, block_count, buf); - return result; + + 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"); +} + +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; + + 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]; + return DVD_VIDEO_LB_LEN * block_count; + } else { + if (self->dvd_self->use_read_ahead) + dprintf("cache miss on sector %d\n", sector); + 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; + + 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--; + + if (cache->freeing) + /* when we want to dispose the cache, try freeing it now */ + dvdnav_read_cache_free(cache); + + return DVDNAV_STATUS_OK; } #endif -
--- a/read_cache.h Fri Jul 12 14:02:52 2002 +0000 +++ b/read_cache.h Fri Jul 12 15:46:44 2002 +0000 @@ -32,7 +32,7 @@ /* EXPERIMENTAL: Setting the following to 1 will use an experimental multi-threaded * read-ahead cache. */ -#define _MULTITHREAD_ 1 +#define _MULTITHREAD_ 0 /* Constructor/destructors */ read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self); @@ -42,7 +42,10 @@ void dvdnav_read_cache_clear(read_cache_t *self); /* 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); -/* This function will do the cache read */ -int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t *buf); +/* This function will do the cache read. + * The buffer handed in must be malloced to take one dvd block. + * On a cache hit, a different buffer will be returned though. + * Those buffers must _never_ be freed. */ +int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf); #endif /* __DVDNAV_READ_CACHE_H */