From 84e2cacec69c73712100de31585da3fe96c94704 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Thu, 24 Dec 2015 17:49:09 +0100 Subject: Split internal CacheManager AFile class out into it's own file/class CacheAudioFile and improve interface. --- src/cachemanager.cc | 660 ++++++++++++++++++++++++---------------------------- 1 file changed, 303 insertions(+), 357 deletions(-) (limited to 'src/cachemanager.cc') diff --git a/src/cachemanager.cc b/src/cachemanager.cc index 4cc1fa5..b2e10ac 100644 --- a/src/cachemanager.cc +++ b/src/cachemanager.cc @@ -3,7 +3,7 @@ * cachemanager.cc * * Fri Apr 10 10:39:24 CEST 2015 - * Copyright 2015 Jonas Suhr Christensen + * Copyright 2015 Jonas Suhr Christensen * jsc@umbraculum.org ****************************************************************************/ @@ -30,436 +30,382 @@ #include #include -#include - #include +#include "cacheaudiofile.h" + #define CHUNKSIZE(x) (x * CHUNK_MULTIPLIER) -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; - std::string filename; -}; - -static void readChunk(AFile* file, - std::list& channels, - size_t pos, size_t num_samples) -{ - 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) { - printf("pos > sf_info.frames\n"); - return; - } - - sf_seek(fh, pos, SEEK_SET); - - size_t size = sf_info.frames - pos; - if(size > num_samples) size = num_samples; - - 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. - } - - size_t read_size = sf_readf_float(fh, read_buffer, size); - (void)read_size; - - for(auto it = channels.begin(); it != channels.end(); ++it) { - size_t channel = it->channel; - sample_t *data = it->samples; - for (size_t i = 0; i < size; i++) { - data[i] = read_buffer[(i * sf_info.channels) + channel]; - } - } - - for(auto it = channels.begin(); it != channels.end(); ++it) { - *(it->ready) = true; - } -} CacheManager::CacheManager() - : framesize(0) - , nodata(NULL) + : framesize(0) + , nodata(nullptr) { } CacheManager::~CacheManager() { - deinit(); - delete[] nodata; + deinit(); + delete[] nodata; } void CacheManager::init(size_t poolsize, bool threaded) { - this->threaded = threaded; - - id2cache.resize(poolsize); - for(size_t i = 0; i < poolsize; i++) { - availableids.push_back(i); - } - - running = true; - if(threaded) { - run(); - sem_run.wait(); - } + this->threaded = threaded; + + id2cache.resize(poolsize); + for(size_t i = 0; i < poolsize; ++i) + { + availableids.push_back(i); + } + + running = true; + if(threaded) + { + run(); + sem_run.wait(); + } } void CacheManager::deinit() { - if(!running) return; - running = false; - if(threaded) { - sem.post(); - wait_stop(); - } + if(!running) return; + running = false; + if(threaded) + { + sem.post(); + wait_stop(); + } } -// Invariant: initial_samples_needed < preloaded audio data +// Invariant: initial_samples_needed < preloaded audio data 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()) { - id = CACHE_DUMMYID; - } else { - id = availableids.front(); - availableids.pop_front(); - } - } - - if(id == CACHE_DUMMYID) { - assert(nodata); - 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.afile = afile; - c.channel = channel; - - // 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; - - // 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.afile, c.channel, c.pos, c.back, &c.ready); - pushEvent(e); - } - - return c.preloaded_samples; // return preloaded data + 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()) + { + id = CACHE_DUMMYID; + } + else + { + id = availableids.front(); + availableids.pop_front(); + } + } + + if(id == CACHE_DUMMYID) + { + assert(nodata); + return nodata; + } + + CacheAudioFile& afile = files.getAudioFile(file->filename); + + cache_t c; + c.afile = &afile; + c.channel = channel; + + // next call to 'next' will read from this point. + c.localpos = initial_samples_needed; + + c.front = nullptr; // This is allocated when needed. + c.back = nullptr; // 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; + + // 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 == nullptr) + { + c.back = new sample_t[CHUNKSIZE(framesize)]; + } + + cevent_t e = + createLoadNextEvent(c.afile, c.channel, c.pos, c.back, &c.ready); + pushEvent(e); + } + + return c.preloaded_samples; // return preloaded data } -sample_t *CacheManager::next(cacheid_t id, size_t &size) +sample_t *CacheManager::next(cacheid_t id, size_t &size) { - size = framesize; - - if(id == CACHE_DUMMYID) { - assert(nodata); - return nodata; - } - - cache_t& c = id2cache[id]; - - 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? - return nodata; - } - - // Swap buffers - std::swap(c.front, c.back); - - // Next time we go here we have already read the first frame. - c.localpos = framesize; - - c.pos += CHUNKSIZE(framesize); - - if(c.pos < c.afile->sf_info.frames) { - if(c.back == NULL) { - c.back = new sample_t[CHUNKSIZE(framesize)]; - } - - cevent_t e = createLoadNextEvent(c.afile, c.channel, c.pos, c.back, &c.ready); - pushEvent(e); - } - - if(!c.front) { - printf("We shouldn't get here... ever!\n"); - assert(false); - return nodata; - } - - return c.front; + size = framesize; + + if(id == CACHE_DUMMYID) + { + assert(nodata); + return nodata; + } + + cache_t& c = id2cache[id]; + + 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 = nullptr; // 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? + return nodata; + } + + // Swap buffers + std::swap(c.front, c.back); + + // Next time we go here we have already read the first frame. + c.localpos = framesize; + + c.pos += CHUNKSIZE(framesize); + + if(c.pos < c.afile->getSize()) + { + if(c.back == nullptr) + { + c.back = new sample_t[CHUNKSIZE(framesize)]; + } + + cevent_t e = createLoadNextEvent(c.afile, c.channel, c.pos, c.back, &c.ready); + pushEvent(e); + } + + if(!c.front) + { + printf("We shouldn't get here... ever!\n"); + assert(false); + return nodata; + } + + return c.front; } void CacheManager::close(cacheid_t id) { - if(id == CACHE_DUMMYID) { - return; - } + if(id == CACHE_DUMMYID) + { + return; + } - cevent_t e = createCloseEvent(id); - pushEvent(e); + cevent_t e = createCloseEvent(id); + pushEvent(e); } void CacheManager::setFrameSize(size_t framesize) { - if(framesize > this->framesize) { - delete[] nodata; - nodata = new sample_t[framesize]; - - for(size_t i = 0; i < framesize; i++) { - nodata[i] = 0; - } - } - - this->framesize = framesize; + if(framesize > this->framesize) + { + delete[] nodata; + nodata = new sample_t[framesize]; + + for(size_t i = 0; i < framesize; ++i) + { + nodata[i] = 0; + } + } + + this->framesize = framesize; } void CacheManager::setAsyncMode(bool async) { - // TODO: Clean out read queue. - // TODO: Block until reader thread is idle, otherwise we might screw up the - // buffers...? - threaded = async; + // TODO: Clean out read queue. + // TODO: Block until reader thread is idle, otherwise we might screw up the + // buffers...? + threaded = async; } -void CacheManager::handleLoadNextEvent(cevent_t &e) +void CacheManager::handleLoadNextEvent(cevent_t &event) { - assert(files.find(e.afile->filename) != files.end()); - readChunk(files[e.afile->filename], e.channels, e.pos, CHUNKSIZE(framesize)); + event.afile->readChunk(event.channels, event.pos, CHUNKSIZE(framesize)); } -void CacheManager::handleCloseEvent(cevent_t &e) +void CacheManager::handleCloseEvent(cevent_t &e) { - cache_t& c = id2cache[e.id]; - - { - MutexAutolock l(m_events); - - auto f = files.find(c.afile->filename); - - if(f != files.end()) { - // Decrease ref count and close file if needed (in AFile destructor). - files[c.afile->filename]->ref--; - if(files[c.afile->filename]->ref == 0) { - delete f->second; - files.erase(f); - } - } - } - - delete[] c.front; - delete[] c.back; - - { - MutexAutolock l(m_ids); - availableids.push_back(e.id); - } + handleCloseCache(e.id); +} + +void CacheManager::handleCloseCache(cacheid_t& cacheid) +{ + cache_t& cache = id2cache[cacheid]; + { + MutexAutolock l(m_events); + + files.release(cache.afile->getFilename()); + } + + delete[] cache.front; + delete[] cache.back; + + { + MutexAutolock l(m_ids); + availableids.push_back(cacheid); + } } void CacheManager::handleEvent(cevent_t &e) { - switch(e.cmd) { - case LOADNEXT: - handleLoadNextEvent(e); - break; - case CLOSE: - handleCloseEvent(e); - break; - } + switch(e.cmd) + { + case LOADNEXT: + handleLoadNextEvent(e); + break; + case CLOSE: + handleCloseEvent(e); + break; + } } void CacheManager::thread_main() { - sem_run.post(); // Signal that the thread has been started + sem_run.post(); // Signal that the thread has been started - while(running) { - sem.wait(); + while(running) + { + sem.wait(); - m_events.lock(); - if(eventqueue.empty()) { - m_events.unlock(); - continue; - } + m_events.lock(); + if(eventqueue.empty()) + { + m_events.unlock(); + continue; + } - cevent_t e = eventqueue.front(); - eventqueue.pop_front(); - m_events.unlock(); + cevent_t e = eventqueue.front(); + eventqueue.pop_front(); + m_events.unlock(); - // TODO: Skip event if e.pos < cache.pos - // if(!e.active) continue; + // TODO: Skip event if e.pos < cache.pos + // if(!e.active) continue; - handleEvent(e); - } + handleEvent(e); + } } void CacheManager::pushEvent(cevent_t& e) { - if(!threaded) { - handleEvent(e); - return; - } - - { - MutexAutolock l(m_events); - - bool found = false; - - if(e.cmd == LOADNEXT) { - for(auto it = eventqueue.begin(); it != eventqueue.end(); ++it) { - auto& event = *it; - if((event.cmd == LOADNEXT) && - (e.afile->filename == event.afile->filename) && - (e.pos == event.pos)) { - // Append channel and buffer to the existing event. - event.channels.insert(event.channels.end(), e.channels.begin(), e.channels.end()); - found = true; - break; - } - } - } - - if(!found) { - // The event was not already on the list, create a new one. - eventqueue.push_back(e); - } - } - - sem.post(); + if(!threaded) + { + handleEvent(e); + return; + } + + { + MutexAutolock l(m_events); + + bool found = false; + + if(e.cmd == LOADNEXT) + { + for(auto it = eventqueue.begin(); it != eventqueue.end(); ++it) + { + auto& event = *it; + if((event.cmd == LOADNEXT) && + (e.afile->getFilename() == event.afile->getFilename()) && + (e.pos == event.pos)) + { + // Append channel and buffer to the existing event. + event.channels.insert(event.channels.end(), e.channels.begin(), + e.channels.end()); + found = true; + break; + } + } + } + + if(!found) + { + // The event was not already on the list, create a new one. + eventqueue.push_back(e); + } + } + + sem.post(); } CacheManager::cevent_t -CacheManager::createLoadNextEvent(AFile *afile, size_t channel, size_t pos, - sample_t* buffer, volatile bool* ready) +CacheManager::createLoadNextEvent(CacheAudioFile *afile, size_t channel, + size_t pos, sample_t* buffer, + volatile bool* ready) { - cevent_t e; - e.cmd = LOADNEXT; - e.pos = pos; - e.afile = afile; + cevent_t e; + e.cmd = LOADNEXT; + e.pos = pos; + e.afile = afile; - CacheManager::Channel c; - c.channel = channel; - c.samples = buffer; + CacheChannel c; + c.channel = channel; + c.samples = buffer; - *ready = false; - c.ready = ready; + *ready = false; + c.ready = ready; - e.channels.insert(e.channels.end(), c); + e.channels.insert(e.channels.end(), c); - return e; + return e; } CacheManager::cevent_t CacheManager::createCloseEvent(cacheid_t id) { - cevent_t e; - e.cmd = CLOSE; - e.id = id; - return e; + cevent_t e; + e.cmd = CLOSE; + e.id = id; + return e; } -- cgit v1.2.3