Skip to content

Commit

Permalink
o First shot at gapless. Works pretty well for my test-case.
Browse files Browse the repository at this point in the history
  • Loading branch information
hzeller committed Sep 21, 2012
1 parent 5098e07 commit 08bbf28
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 60 deletions.
1 change: 1 addition & 0 deletions README
Expand Up @@ -97,6 +97,7 @@ Options: (in sequence of usefulness)
You can supply this option multiple times:
you'll get a drop-down select on the HTTP status page.
-p <port> : Port to run the HTTP status server on.
-g : 'gapless': convolve adjacent files (experimental).
-D : Moderate volume Folve debug messages to syslog.
Can then also be toggled in the UI.
-f : Operate in foreground; useful for debugging.
Expand Down
7 changes: 6 additions & 1 deletion file-handler.h
Expand Up @@ -22,7 +22,8 @@
// Status about some handler, filled in by various subsystem.
struct HandlerStats {
HandlerStats()
: duration_seconds(-1), progress(-1), status(OPEN), last_access(0) {}
: duration_seconds(-1), progress(-1), status(OPEN), last_access(0),
in_gapless(false), out_gapless(false) {}
std::string filename; // filesystem name.
std::string format; // File format info if recognized.
std::string message; // Per file (error) message if any.
Expand All @@ -32,8 +33,11 @@ struct HandlerStats {
enum Status { OPEN, IDLE, RETIRED };
Status status; // Status of this file handler.
double last_access; // Last access in hi-res seconds since epoch.
bool in_gapless; // Were we handed a processor to continue.
bool out_gapless; // Did we pass on our processor.
};

class SoundProcessor;
// A handler that deals with operations on files. Since we only provide read
// access, this is limited to very few operations.
// Closing in particular is not done by this file handler as it might
Expand All @@ -52,6 +56,7 @@ class FileHandler {

// Get handler status.
virtual void GetHandlerStatus(struct HandlerStats *s) = 0;
virtual bool AcceptProcessor(SoundProcessor *s) { return false; }

private:
const int filter_id_;
Expand Down
105 changes: 83 additions & 22 deletions folve-filesystem.cc
Expand Up @@ -16,6 +16,7 @@
#include "folve-filesystem.h"

#include <FLAC/metadata.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sndfile.h>
Expand Down Expand Up @@ -107,16 +108,17 @@ class SndFileHandler :
// convolution filter configuration available.
// "partial_file_info" will be set to information known so far, including
// error message.
static FileHandler *Create(int filedes, const char *fs_path,
const char *underlying_file,
static FileHandler *Create(FolveFilesystem *fs,
int filedes, const char *fs_path,
const std::string &underlying_file,
int filter_id,
const std::string &zita_config_dir,
HandlerStats *partial_file_info) {
SF_INFO in_info;
memset(&in_info, 0, sizeof(in_info));
SNDFILE *snd = sf_open_fd(filedes, SFM_READ, &in_info, 0);
if (snd == NULL) {
DebugLogf("File %s: %s", underlying_file, sf_strerror(NULL));
DebugLogf("File %s: %s", underlying_file.c_str(), sf_strerror(NULL));
partial_file_info->message = sf_strerror(NULL);
return NULL;
}
Expand Down Expand Up @@ -150,19 +152,19 @@ class SndFileHandler :
&config_path);
if (found_config) {
DebugLogf("File %s, %.1fkHz, %d Bit, %d:%02d: filter config %s",
underlying_file, in_info.samplerate / 1000.0, bits,
underlying_file.c_str(), in_info.samplerate / 1000.0, bits,
seconds / 60, seconds % 60,
config_path.c_str());
} else {
DebugLogf("File %s: couldn't find filter config %s...%s",
underlying_file,
underlying_file.c_str(),
path_choices[0].c_str(), path_choices[max_choice].c_str());
partial_file_info->message = "Missing ( " + path_choices[0]
+ "<br/> ... " + path_choices[max_choice] + " )";
sf_close(snd);
return NULL;
}
return new SndFileHandler(fs_path, filter_id,
return new SndFileHandler(fs, fs_path, filter_id,
underlying_file, filedes, snd, in_info,
*partial_file_info, config_path);
}
Expand Down Expand Up @@ -238,11 +240,12 @@ class SndFileHandler :

private:
// TODO(hzeller): trim parameter list.
SndFileHandler(const char *fs_path, int filter_id,
const char *underlying_file, int filedes, SNDFILE *snd_in,
SndFileHandler(FolveFilesystem *fs, const char *fs_path, int filter_id,
const std::string &underlying_file,
int filedes, SNDFILE *snd_in,
const SF_INFO &in_info, const HandlerStats &file_info,
const std::string &config_path)
: FileHandler(filter_id),
: FileHandler(filter_id), fs_(fs),
filedes_(filedes), snd_in_(snd_in), in_info_(in_info),
config_path_(config_path),
base_stats_(file_info),
Expand Down Expand Up @@ -333,11 +336,30 @@ class SndFileHandler :
out_buffer->HeaderFinished();
}

virtual bool AcceptProcessor(SoundProcessor *processor) {
if (processor_ != NULL || !input_frames_left_)
return false; // We already have one.
processor_ = processor;
if (!processor_->is_input_buffer_complete()) {
input_frames_left_ -= processor_->FillBuffer(snd_in_);
}
base_stats_.in_gapless = true;
return true;
}

static std::string Dirname(const std::string &filename) {
return filename.substr(0, filename.find_last_of('/') + 1);
}

virtual bool AddMoreSoundData() {
if (processor_ && processor_->pending_writes() > 0) {
processor_->WriteProcessed(snd_out_, processor_->pending_writes());
return input_frames_left_;
}
if (!input_frames_left_)
return false;
if (!processor_) {
// First time we're called.
// First time we're called and we don't have any processor yet.
processor_ = SoundProcessor::Create(config_path_, in_info_.samplerate,
in_info_.channels);
if (processor_ == NULL) {
Expand All @@ -360,14 +382,38 @@ class SndFileHandler :
Close();
return false;
}
processor_->WriteProcessed(snd_out_, r);
input_frames_left_ -= r;
if (!input_frames_left_ && !processor_->is_input_buffer_complete()
&& fs_->gapless_processing()) {
typedef std::set<std::string> DirSet;
DirSet dirents;
fs_->ListDirectory(Dirname(base_stats_.filename), &dirents);
DirSet::const_iterator found = dirents.upper_bound(base_stats_.filename);
FileHandler *next_file = NULL;
const bool passed_processor
= (found != dirents.end()
&& (next_file = fs_->GetOrCreateHandler(found->c_str()))
&& next_file->AcceptProcessor(processor_));
if (passed_processor) {
DebugLogf("Gapless pass-on from '%s' to '%s'",
base_stats_.filename.c_str(), found->c_str());
}
processor_->WriteProcessed(snd_out_, r);
if (passed_processor) {
base_stats_.out_gapless = true;
processor_ = NULL;
}
if (next_file) fs_->Close(found->c_str(), next_file);
} else {
processor_->WriteProcessed(snd_out_, r);
}
if (input_frames_left_ == 0) {
Close();
}
return input_frames_left_;
}

// TODO add as a utility function to ConversionBuffer ?
void CopyBytes(int fd, off_t pos, ConversionBuffer *out, size_t len) {
char buf[256];
while (len > 0) {
Expand Down Expand Up @@ -466,6 +512,7 @@ class SndFileHandler :
return memcmp(flac_magic, "fLaC", sizeof(flac_magic)) == 0;
}

FolveFilesystem *const fs_;
const int filedes_;
SNDFILE *const snd_in_;
const SF_INFO in_info_;
Expand All @@ -488,19 +535,20 @@ class SndFileHandler :
} // namespace

FolveFilesystem::FolveFilesystem()
: current_cfg_index_(0), debug_ui_enabled_(false),
: current_cfg_index_(0), debug_ui_enabled_(false), gapless_processing_(false),
open_file_cache_(3), total_file_openings_(0), total_file_reopen_(0) {
config_dirs_.push_back(""); // The first config is special: empty.
}

FileHandler *FolveFilesystem::CreateFromDescriptor(int filedes,
int cfg_idx,
const char *fs_path,
const char *underlying_file) {
FileHandler *FolveFilesystem::CreateFromDescriptor(
int filedes,
int cfg_idx,
const char *fs_path,
const std::string &underlying_file) {
HandlerStats file_info;
file_info.filename = fs_path;
if (cfg_idx != 0) {
FileHandler *filter = SndFileHandler::Create(filedes, fs_path,
FileHandler *filter = SndFileHandler::Create(this, filedes, fs_path,
underlying_file,
cfg_idx,
config_dirs()[cfg_idx],
Expand All @@ -520,19 +568,18 @@ std::string FolveFilesystem::CacheKey(int config_idx, const char *fs_path) {
return result;
}

// Implementation of the C functions in filter-interface.h
FileHandler *FolveFilesystem::CreateHandler(const char *fs_path,
const char *underlying_path) {
FileHandler *FolveFilesystem::GetOrCreateHandler(const char *fs_path) {
const int config_idx = current_cfg_index_;
const std::string cache_key = CacheKey(config_idx, fs_path);
const std::string underlying_file = underlying_dir() + fs_path;
FileHandler *handler = open_file_cache_.FindAndPin(cache_key);
if (handler == NULL) {
int filedes = open(underlying_path, O_RDONLY);
int filedes = open(underlying_file.c_str(), O_RDONLY);
if (filedes < 0)
return NULL;
++total_file_openings_;
handler = CreateFromDescriptor(filedes, config_idx,
fs_path, underlying_path);
fs_path, underlying_file);
handler = open_file_cache_.InsertPinned(cache_key, handler);
} else {
++total_file_reopen_;
Expand All @@ -555,6 +602,20 @@ void FolveFilesystem::Close(const char *fs_path, const FileHandler *handler) {
open_file_cache_.Unpin(cache_key);
}

bool FolveFilesystem::ListDirectory(const std::string &fs_dir,
std::set<std::string> *files) {
const std::string real_dir = underlying_dir() + fs_dir;
DIR *dp = opendir(real_dir.c_str());
if (dp == NULL) return false;
struct dirent *dent;
while ((dent = readdir(dp)) != NULL) {
std::string x = fs_dir + dent->d_name;
files->insert(fs_dir + dent->d_name);
}
closedir(dp);
return true;
}

void FolveFilesystem::SwitchCurrentConfigIndex(int i) {
if (i < 0 || i >= (int) config_dirs_.size())
return;
Expand Down
20 changes: 14 additions & 6 deletions folve-filesystem.h
Expand Up @@ -21,6 +21,7 @@

#include <string>
#include <vector>
#include <set>

#include "file-handler-cache.h"
#include "file-handler.h"
Expand Down Expand Up @@ -56,16 +57,19 @@ class FolveFilesystem {
// Create a new filter given the filesystem path and the underlying
// path.
// Returns NULL, if it cannot be created.
FileHandler *CreateHandler(const char *fs_path,
const char *underlying_path);

// Return dynamic size of file.
int StatByFilename(const char *fs_path, struct stat *st);
FileHandler *GetOrCreateHandler(const char *fs_path);

// Inform filesystem that this file handler is not needed anymore
// (FS still might consider keeping it around for a while).
void Close(const char *fs_path, const FileHandler *handler);

// Return dynamic size of file.
int StatByFilename(const char *fs_path, struct stat *st);

// List files in given filesystem directory. Returns a set of filesystem
// paths of existing files.
bool ListDirectory(const std::string &fs_dir, std::set<std::string> *files);

FileHandlerCache *handler_cache() { return &open_file_cache_; }

// Sets more fine grained debug log messages.
Expand All @@ -75,6 +79,9 @@ class FolveFilesystem {
void set_debug_ui_enabled(bool b) { debug_ui_enabled_ = b; }
bool is_debug_ui_enabled() const { return debug_ui_enabled_; }

void set_gapless_processing(bool b) { gapless_processing_ = b; }
bool gapless_processing() const { return gapless_processing_; }

// Some stats.
int total_file_openings() { return total_file_openings_; }
int total_file_reopen() { return total_file_reopen_; }
Expand All @@ -85,12 +92,13 @@ class FolveFilesystem {

FileHandler *CreateFromDescriptor(int filedes, int cfg_idx,
const char *fs_path,
const char *underlying_file);
const std::string &underlying_file);

std::string underlying_dir_;
std::vector<std::string> config_dirs_;
int current_cfg_index_;
bool debug_ui_enabled_;
bool gapless_processing_;
FileHandlerCache open_file_cache_;
int total_file_openings_;
int total_file_reopen_;
Expand Down

0 comments on commit 08bbf28

Please sign in to comment.