Implemented spectrum cache aging/memory limiting.

Originally committed to SVN as r1317.
This commit is contained in:
Niels Martin Hansen 2007-06-30 14:40:52 +00:00
parent 7b3674abcd
commit 336f0621d7
5 changed files with 206 additions and 45 deletions

View File

@ -35,6 +35,10 @@
//
#include <assert.h>
#include <vector>
#include <list>
#include <utility>
#include <algorithm>
#include "audio_spectrum.h"
#include "fft.h"
#include "colorspace.h"
@ -44,14 +48,54 @@
// Audio spectrum FFT data cache
AudioSpectrumCache::CacheLine AudioSpectrumCache::null_line;
unsigned long AudioSpectrumCache::line_length;
// Spectrum cache basically caches the raw result of FFT
class AudioSpectrumCache {
public:
// Type of a single FFT result line
typedef std::vector<float> CacheLine;
void AudioSpectrumCache::SetLineLength(unsigned long new_length)
{
// Types for cache aging
typedef unsigned int CacheAccessTime;
struct CacheAgeData {
CacheAccessTime access_time;
unsigned long first_line;
unsigned long num_lines; // includes overlap-lines
bool operator< (const CacheAgeData& second) { return access_time < second.access_time; }
CacheAgeData(CacheAccessTime t, unsigned long first, unsigned long num) : access_time(t), first_line(first), num_lines(num) { }
};
typedef std::vector<CacheAgeData> CacheAgeList;
// Get the overlap'th overlapping FFT in FFT group i, generating it if needed
virtual CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time) = 0;
// Get the total number of cache lines currently stored in this cache node's sub tree
virtual size_t GetManagedLineCount() = 0;
// Append to a list of last access times to the cache
virtual void GetLineAccessTimes(CacheAgeList &ages) = 0;
// Delete the cache storage starting with the given line id
// Return true if the object called on is empty and can safely be deleted too
virtual bool KillLine(unsigned long line_id) = 0;
// Set the FFT size used
static void SetLineLength(unsigned long new_length)
{
line_length = new_length;
null_line.resize(new_length, 0);
}
}
virtual ~AudioSpectrumCache() {};
protected:
// A cache line containing only zero-values
static CacheLine null_line;
// The FFT size
static unsigned long line_length;
};
AudioSpectrumCache::CacheLine AudioSpectrumCache::null_line;
unsigned long AudioSpectrumCache::line_length;
// Bottom level FFT cache, holds actual power data itself
@ -62,9 +106,13 @@ private:
unsigned long start, length; // start and end of range
unsigned int overlaps;
CacheAccessTime last_access;
public:
CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created)
CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time)
{
last_access = access_time;
// This check ought to be redundant
if (i >= start && i-start < length)
return data[i - start + overlap*length];
@ -72,6 +120,21 @@ public:
return null_line;
}
size_t GetManagedLineCount()
{
return data.size();
}
void GetLineAccessTimes(CacheAgeList &ages)
{
ages.push_back(CacheAgeData(last_access, start, data.size()));
}
bool KillLine(unsigned long line_id)
{
return start == line_id;
}
FinalSpectrumCache(AudioProvider *provider, unsigned long _start, unsigned long _length, unsigned int _overlaps)
{
start = _start;
@ -149,7 +212,7 @@ private:
AudioProvider *provider;
public:
CacheLine &GetLine(unsigned long i, unsigned int overlap, bool &created)
CacheLine &GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time)
{
if (i >= start && i-start <= length) {
// Determine which sub-cache this line resides in
@ -165,12 +228,46 @@ public:
}
}
return sub_caches[subcache]->GetLine(i, overlap, created);
return sub_caches[subcache]->GetLine(i, overlap, created, access_time);
} else {
return null_line;
}
}
size_t GetManagedLineCount()
{
size_t res = 0;
for (size_t i = 0; i < sub_caches.size(); ++i) {
if (sub_caches[i])
res += sub_caches[i]->GetManagedLineCount();
}
return res;
}
void GetLineAccessTimes(CacheAgeList &ages)
{
for (size_t i = 0; i < sub_caches.size(); ++i) {
if (sub_caches[i])
sub_caches[i]->GetLineAccessTimes(ages);
}
}
bool KillLine(unsigned long line_id)
{
int sub_caches_left = 0;
for (size_t i = 0; i < sub_caches.size(); ++i) {
if (sub_caches[i]) {
if (sub_caches[i]->KillLine(line_id)) {
delete sub_caches[i];
sub_caches[i] = 0;
} else {
sub_caches_left++;
}
}
}
return sub_caches_left == 0;
}
IntermediateSpectrumCache(AudioProvider *_provider, unsigned long _start, unsigned long _length, unsigned int _overlaps, int _depth)
{
provider = _provider;
@ -203,6 +300,91 @@ public:
};
class AudioSpectrumCacheManager {
private:
IntermediateSpectrumCache *cache_root;
unsigned long cache_hits, cache_misses;
AudioSpectrumCache::CacheAccessTime cur_time;
unsigned long max_lines_cached;
public:
AudioSpectrumCache::CacheLine &GetLine(unsigned long i, unsigned int overlap)
{
bool created = false;
AudioSpectrumCache::CacheLine &res = cache_root->GetLine(i, overlap, created, cur_time++);
if (created)
cache_misses++;
else
cache_hits++;
return res;
}
void Age()
{
wxLogDebug(_T("AudioSpectrumCacheManager stats: hits=%u, misses=%u, misses%%=%f, managed lines=%u (max=%u)"), cache_hits, cache_misses, cache_misses/float(cache_hits+cache_misses)*100, cache_root->GetManagedLineCount(), max_lines_cached);
// 0 means no limit
if (max_lines_cached == 0)
return;
// No reason to proceed with complicated stuff if the count is too small
// (FIXME: does this really pay off?)
if (cache_root->GetManagedLineCount() < max_lines_cached)
return;
// Get and sort ages
AudioSpectrumCache::CacheAgeList ages;
cache_root->GetLineAccessTimes(ages);
std::sort(ages.begin(), ages.end());
// Number of lines we have found used so far
// When this exceeds max_lines_caches go into kill-mode
unsigned long cumulative_lines = 0;
// Run backwards through the line age list (the most recently accessed items are at end)
AudioSpectrumCache::CacheAgeList::reverse_iterator it = ages.rbegin();
// Find the point where we have too many lines cached
while (cumulative_lines < max_lines_cached) {
if (it == ages.rend()) {
wxLogDebug(_T("AudioSpectrumCacheManager done aging did not exceed max_lines_cached"));
return;
}
cumulative_lines += it->num_lines;
++it;
}
// By here, we have exceeded max_lines_cached so backtrack one
--it;
// And now start cleaning up
for (; it != ages.rend(); ++it) {
cache_root->KillLine(it->first_line);
}
wxLogDebug(_T("AudioSpectrumCacheManager done aging, managed lines now=%u (max=%u)"), cache_root->GetManagedLineCount(), max_lines_cached);
assert(cache_root->GetManagedLineCount() < max_lines_cached);
}
AudioSpectrumCacheManager(AudioProvider *provider, unsigned long line_length, unsigned long num_lines, unsigned int num_overlaps)
{
cache_hits = cache_misses = 0;
cur_time = 0;
cache_root = new IntermediateSpectrumCache(provider, 0, num_lines, num_overlaps, 0);
// option is stored in megabytes, but we want number of bytes
unsigned long max_cache_size = Options.AsInt(_T("Audio Spectrum Memory Max")) * 1024 * 1024;
unsigned long line_size = sizeof(AudioSpectrumCache::CacheLine::value_type) * line_length;
max_lines_cached = max_cache_size / line_size;
}
~AudioSpectrumCacheManager()
{
delete cache_root;
}
};
// AudioSpectrum
AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
@ -228,7 +410,7 @@ AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
num_lines = (unsigned long)_num_lines;
AudioSpectrumCache::SetLineLength(line_length);
cache = new IntermediateSpectrumCache(provider, 0, num_lines, fft_overlaps, 0);
cache = new AudioSpectrumCacheManager(provider, line_length, num_lines, fft_overlaps);
power_scale = 1;
minband = Options.AsInt(_T("Audio Spectrum Cutoff"));
@ -261,9 +443,6 @@ void AudioSpectrum::RenderRange(__int64 range_start, __int64 range_end, bool sel
unsigned long first_line = (unsigned long)(fft_overlaps * range_start / line_length / 2);
unsigned long last_line = (unsigned long)(fft_overlaps * range_end / line_length / 2);
unsigned int cache_hits=0, cache_misses=0;
bool was_cache_miss;
float *power = new float[line_length];
int last_imgcol_rendered = -1;
@ -290,15 +469,12 @@ void AudioSpectrum::RenderRange(__int64 range_start, __int64 range_end, bool sel
if (imgcol <= last_imgcol_rendered)
continue;
was_cache_miss = false;
AudioSpectrumCache::CacheLine &line = cache->GetLine(baseline, overlap, was_cache_miss);
AudioSpectrumCache::CacheLine &line = cache->GetLine(baseline, overlap);
++overlap;
if (overlap >= fft_overlaps) {
overlap = 0;
++baseline;
}
if (was_cache_miss) cache_misses++;
else cache_hits++;
// Apply a "compressed" scaling to the signal power
for (unsigned int j = 0; j < line_length; j++) {
@ -365,7 +541,7 @@ void AudioSpectrum::RenderRange(__int64 range_start, __int64 range_end, bool sel
delete[] power;
wxLogDebug(_T("Rendered spectrum: %u cache hits, %u misses"), cache_hits, cache_misses);
cache->Age();
}

View File

@ -38,36 +38,17 @@
#define AUDIO_SPECTRUM_H
#include <wx/wxprec.h>
#include <vector>
#include "audio_provider.h"
// Spectrum cache basically caches the raw result of FFT
class AudioSpectrumCache {
public:
// Type of a single FFT result line
typedef std::vector<float> CacheLine;
// Get the overlap'th overlapping FFT in FFT group i, generating it if needed
virtual CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created) = 0;
// Set the FFT size used
static void SetLineLength(unsigned long new_length);
virtual ~AudioSpectrumCache() {};
protected:
// A cache line containing only zero-values
static CacheLine null_line;
// The FFT size
static unsigned long line_length;
};
// Specified and implemented in cpp file, interface is private to spectrum code
class AudioSpectrumCacheManager;
class AudioSpectrum {
private:
// Data provider
AudioSpectrumCache *cache;
AudioSpectrumCacheManager *cache;
// Colour pallettes
unsigned char colours_normal[256*3];

View File

@ -55,11 +55,13 @@ Please visit http://aegisub.net to download latest version
o Misc. changes and bugfixes in karaoke mode. Using the syllable splitter should be easier now. (jfs)
o Fixed loading of Avisynth Scripts as audio. (AMZ)
- Changes to Audio Spectrum: (jfs)
o The calculated FFT data are now cached, so things should be faster
o Actual signal power is now more accurately represented
o The palette is changed
o The selection is no longer shown by ugly reverse colour but with a different palette instead
o Use vertical zoom slider to amplify/dampen displayed signal strength (useful for better visualisation of quiet sections, or easier picking out the dominating frequencies in noisy sections)
o The calculated FFT data are now cached, so things should be faster.
- The maximum size of the cache can be configured. Default is unlimited, which provides the best performance assuming enough memory is available.
o The quality of the spectrum can be easier configured, better quality requires more CPU and memory.
o Actual signal power is now more accurately represented.
o The palette is changed.
o The selection is no longer shown by ugly reverse colour but with a different palette instead.
o Use vertical zoom slider to amplify/dampen displayed signal strength. (Useful for better visualisation of quiet sections, or easier picking out the dominating frequencies in noisy sections.)
- Style Manager changes:
o Automatically select the style of the current line when Style Manager is opened. (jfs)
o Style storages with invalid characters are now caught, and the invalid characters replaced with safe ones. Previously such storages seemed to be created correctly but were never stored to disk. (jfs)

View File

@ -530,6 +530,7 @@ DialogOptions::DialogOptions(wxWindow *parent)
AddTextControl(audioAdvPage,audioAdvSizer1,_("Spectrum cutoff"),_T("Audio spectrum cutoff"),TEXT_TYPE_NUMBER);
wxString spectrum_quality_choices[] = { _("0 - Regular quality"), _("1 - Better quality"), _("2 - High quality"), _("3 - Insane quality") };
AddComboControl(audioAdvPage,audioAdvSizer1,_("Spectrum quality"),_T("Audio spectrum quality"),wxArrayString(4,spectrum_quality_choices));
AddTextControl(audioAdvPage,audioAdvSizer1,_("Spectrum cache memory max (MB)"),_T("Audio spectrum memory max"),TEXT_TYPE_NUMBER);
audioAdvSizer1->AddGrowableCol(0,1);
// Main sizer

View File

@ -193,6 +193,7 @@ void OptionsManager::LoadDefaults(bool onlyDefaults) {
// Technically these can do with just the spectrum object being re-created
SetInt(_T("Audio Spectrum Cutoff"),0);
SetInt(_T("Audio Spectrum Quality"),0);
SetInt(_T("Audio Spectrum Memory Max"),0); // megabytes
// Automation
// The path changes only take effect when a script is (re)loaded but Automatic should be good enough, it certainly doesn't warrart a restart