From 7f19dfdba76e9d127f8f6c1e1c3c547755e7d7c1 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Sun, 19 Jul 2015 14:06:10 +0200 Subject: Make cachemanager behave as if it isn't there when CHUNK_MULTIPLIER is big enough to contain all data in an audio file. Add refcounted file handling in cachemanager. --- src/cachemanager.cc | 237 +++++++++++++++++++++++++++++++++++----------------- src/cachemanager.h | 13 ++- 2 files changed, 171 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/cachemanager.cc b/src/cachemanager.cc index 4053f05..63f99bc 100644 --- a/src/cachemanager.cc +++ b/src/cachemanager.cc @@ -34,23 +34,48 @@ #include -#define BUFFER_SIZE 4092 - #define CHUNKSIZE(x) (x * CHUNK_MULTIPLIER) -static size_t readChunk(std::string filename, int filechannel, size_t pos, - size_t num_samples, sample_t* buf) -{ +class AFile { +public: + AFile(std::string filename) + : ref(0) + , filename(filename) + { + fh = sf_open(filename.c_str(), SFM_READ, &sf_info); + if(!fh) { + ERR(audiofile,"SNDFILE Error (%s): %s\n", + filename.c_str(), sf_strerror(fh)); + //return 0; + } + } + + ~AFile() + { + sf_close(fh); + fh = NULL; + } + + int ref; + SNDFILE* fh; SF_INFO sf_info; - SNDFILE *fh = sf_open(filename.c_str(), SFM_READ, &sf_info); - if(!fh) { - ERR(audiofile,"SNDFILE Error (%s): %s\n", - filename.c_str(), sf_strerror(fh)); - return 0; + std::string filename; +}; + +static void readChunk(AFile* file, int filechannel, size_t pos, + size_t num_samples, sample_t* buf) +{ + SNDFILE* fh = file->fh; + SF_INFO& sf_info = file->sf_info; + + if(fh == NULL) { + printf("File handle is null.\n"); + return; } if(pos > sf_info.frames) { - return 0; + printf("pos > sf_info.frames\n"); + return; } sf_seek(fh, pos, SEEK_SET); @@ -58,31 +83,24 @@ static size_t readChunk(std::string filename, int filechannel, size_t pos, size_t size = sf_info.frames - pos; if(size > num_samples) size = num_samples; - sample_t* data = buf; - if(sf_info.channels == 1) { - size = sf_readf_float(fh, data, size); - } else { - // check filechannel exists - if(filechannel >= sf_info.channels) { - filechannel = sf_info.channels - 1; - } - sample_t buffer[BUFFER_SIZE]; - int readsize = BUFFER_SIZE / sf_info.channels; - int totalread = 0; - int read; - do { - read = sf_readf_float(fh, buffer, readsize); - for (int i = 0; i < read && totalread < (int)size; i++) { - data[totalread++] = buffer[i * sf_info.channels + filechannel]; - } - } while(read > 0 && totalread < (int)size && totalread < sf_info.frames); - // set data size to total bytes read - size = totalread; + static sample_t *read_buffer = NULL; + static size_t read_buffer_size = 0; + + if((size * sf_info.channels) > read_buffer_size) { + delete[] read_buffer; + read_buffer_size = size * sf_info.channels; + read_buffer = new sample_t[read_buffer_size]; + // TODO: This buffer is never free'd on app shutdown. } - sf_close(fh); + size_t read_size = sf_readf_float(fh, read_buffer, size); + (void)read_size; - return size; + size_t channel = filechannel; + sample_t *data = buf; + for (size_t i = 0; i < size; i++) { + data[i] = read_buffer[(i * sf_info.channels) + channel]; + } } CacheManager::CacheManager() @@ -124,10 +142,16 @@ void CacheManager::deinit() } // Invariant: initial_samples_needed < preloaded audio data -// Proposal: preloaded > 2 x CHUNKSIZE? So that we can fill c.front immediatly on open sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed, int channel, cacheid_t &id) { + if(!file->isValid()) { + // File preload not yet ready - skip this sample. + id = CACHE_DUMMYID; + assert(nodata); + return nodata; + } + { MutexAutolock l(m_ids); if(availableids.empty()) { @@ -143,36 +167,67 @@ sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed, return nodata; } + AFile *afile = NULL; + if(files.find(file->filename) == files.end()) { + afile = new AFile(file->filename); + files[file->filename] = afile; + } else { + afile = files[file->filename]; + } + + // Increase ref count. + afile->ref++; + cache_t c; - c.file = file; + c.file = afile; c.channel = channel; - c.pos = initial_samples_needed; - c.localpos = 0; - c.front = new sample_t[CHUNKSIZE(framesize)]; - c.back = new sample_t[CHUNKSIZE(framesize)]; - - size_t size = CHUNKSIZE(framesize); - if(size > (file->preloadedsize - c.pos)) size = (file->preloadedsize - c.pos); - memcpy(c.front, c.file->data + c.pos, size * sizeof(sample_t)); + + // next call to 'next' will read from this point. + c.localpos = initial_samples_needed; + + c.front = NULL; // This is allocated when needed. + c.back = NULL; // This is allocated when needed. + + // cropped_size is the preload chunk size cropped to sample length. + size_t cropped_size = file->preloadedsize - c.localpos; + cropped_size /= framesize; + cropped_size *= framesize; + cropped_size += initial_samples_needed; + + if(file->preloadedsize == file->size) { + // We have preloaded the entire file, so use it. + cropped_size = file->preloadedsize; + } + + c.preloaded_samples = file->data; + c.preloaded_samples_size = cropped_size; + c.ready = false; - c.pos += size; - // Increase audio ref count + // next read from disk will read from this point. + c.pos = cropped_size;//c.preloaded_samples_size; { MutexAutolock l(m_ids); id2cache[id] = c; } + // NOTE: Below this point we can no longer write to 'c'. // Only load next buffer if there are more data in the file to be loaded... if(c.pos < file->size) { + cache_t& c = id2cache[id]; // Create new writeable 'c'. + + if(c.back == NULL) { + c.back = new sample_t[CHUNKSIZE(framesize)]; + } + cevent_t e = createLoadNextEvent(c.file, c.channel, c.pos, c.back); e.ready = &id2cache[id].ready; pushEvent(e); } - return file->data; // preloaded data + return c.preloaded_samples; // return preloaded data } sample_t *CacheManager::next(cacheid_t id, size_t &size) @@ -185,32 +240,58 @@ sample_t *CacheManager::next(cacheid_t id, size_t &size) } cache_t& c = id2cache[id]; - if(c.localpos < CHUNKSIZE(framesize)) { - sample_t *s = c.front + c.localpos; - c.localpos += size; - return s; + + if(c.preloaded_samples) { + + // We are playing from memory: + if(c.localpos < c.preloaded_samples_size) { + sample_t *s = c.preloaded_samples + c.localpos; + c.localpos += framesize; + return s; + } + + c.preloaded_samples = NULL; // Start using samples from disk. + + } else { + + // We are playing from cache: + if(c.localpos < CHUNKSIZE(framesize)) { + sample_t *s = c.front + c.localpos; + c.localpos += framesize; + return s; + } } if(!c.ready) { - //printf("#%d: NOT READY!\n", id); // TODO: Count and show in UI? + printf("#%d: NOT READY!\n", id); // TODO: Count and show in UI? + return nodata; } // Swap buffers - sample_t *tmp = c.front; - c.front = c.back; - c.back = tmp; + std::swap(c.front, c.back); - c.localpos = size; // Next time we go here we have already read the first frame. + // Next time we go here we have already read the first frame. + c.localpos = framesize; c.pos += CHUNKSIZE(framesize); - if(c.pos < c.file->size) { + if(c.pos < c.file->sf_info.frames) { + if(c.back == NULL) { + c.back = new sample_t[CHUNKSIZE(framesize)]; + } + cevent_t e = createLoadNextEvent(c.file, c.channel, c.pos, c.back); c.ready = false; e.ready = &c.ready; pushEvent(e); } + if(!c.front) { + printf("We shouldn't get here... ever!\n"); + assert(false); + return nodata; + } + return c.front; } @@ -226,33 +307,40 @@ void CacheManager::close(cacheid_t id) void CacheManager::setFrameSize(size_t framesize) { - this->framesize = framesize; - delete[] nodata; - nodata = new sample_t[framesize]; + if(framesize > this->framesize) { + delete[] nodata; + nodata = new sample_t[framesize]; - for(size_t i = 0; i < framesize; i++) { - nodata[i] = 0; + for(size_t i = 0; i < framesize; i++) { + nodata[i] = 0; + } } + + this->framesize = framesize; } void CacheManager::handleLoadNextEvent(cevent_t &e) { -#if 0 // memcpy - size_t size = CHUNKSIZE(framesize); - if(size > (e.file->size - e.pos)) { - size = (e.file->size - e.pos); - } - memcpy(e.buffer, e.file->data + e.pos, size * sizeof(sample_t)); -#elif 1 // diskread - //memset(e.buffer, 0, CHUNKSIZE(framesize) * sizeof(sample_t)); - readChunk(e.file->filename, e.channel, e.pos, CHUNKSIZE(framesize), e.buffer); -#endif + assert(files.find(e.file->filename) != files.end()); + readChunk(files[e.file->filename], e.channel, e.pos, CHUNKSIZE(framesize), e.buffer); *e.ready = true; } void CacheManager::handleCloseEvent(cevent_t &e) { cache_t& c = id2cache[e.id]; + + auto f = files.find(c.file->filename); + + if(f != files.end()) { + // Decrease ref count and close file if needed (in AFile destructor). + files[c.file->filename]->ref--; + if(files[c.file->filename]->ref == 0) { + delete f->second; + files.erase(f); + } + } + delete[] c.front; delete[] c.back; @@ -260,8 +348,6 @@ void CacheManager::handleCloseEvent(cevent_t &e) MutexAutolock l(m_ids); availableids.push_back(e.id); } - - // TODO: Count down ref counter on c.file and close it if 0. } @@ -308,8 +394,7 @@ void CacheManager::pushEvent(cevent_t e) return; } - // Check that if event should be merged (Maybe by event queue (ie. push - // in front). + // TODO: Check if event should be merged. { MutexAutolock l(m_events); eventqueue.push_back(e); @@ -319,7 +404,7 @@ void CacheManager::pushEvent(cevent_t e) } CacheManager::cevent_t -CacheManager::createLoadNextEvent(AudioFile *file, size_t channel, size_t pos, +CacheManager::createLoadNextEvent(AFile *file, size_t channel, size_t pos, sample_t* buffer) { cevent_t e; diff --git a/src/cachemanager.h b/src/cachemanager.h index d98d66c..a70af35 100644 --- a/src/cachemanager.h +++ b/src/cachemanager.h @@ -45,6 +45,7 @@ class AudioFile; typedef int cacheid_t; +class AFile; //TODO: @@ -139,13 +140,17 @@ private: sample_t *nodata; typedef struct { - AudioFile *file; + AFile *file; size_t channel; size_t pos; //< File possition volatile bool ready; sample_t *front; sample_t *back; size_t localpos; //< Intra buffer (front) position. + + sample_t* preloaded_samples; // NULL means not active. + size_t preloaded_samples_size; + } cache_t; typedef enum { @@ -163,11 +168,11 @@ private: size_t pos; sample_t *buffer; volatile bool *ready; - AudioFile *file; size_t channel; + AFile *file; } cevent_t; - cevent_t createLoadNextEvent(AudioFile *file, size_t channel, size_t pos, + cevent_t createLoadNextEvent(AFile *file, size_t channel, size_t pos, sample_t* buffer); cevent_t createCloseEvent(cacheid_t id); @@ -190,6 +195,8 @@ private: Semaphore sem; Semaphore sem_run; bool running; + + std::map files; }; #endif/*__DRUMGIZMO_CACHEMANAGER_H__*/ -- cgit v1.2.3