Skip to content
Browse files

o First shot at gapless. Works pretty well for my test-case.

  • Loading branch information...
1 parent 5098e07 commit 08bbf282664686cb37c6753250e6eef091c300d9 @hzeller committed Sep 21, 2012
Showing with 133 additions and 60 deletions.
  1. +1 −0 README
  2. +6 −1 file-handler.h
  3. +83 −22 folve-filesystem.cc
  4. +14 −6 folve-filesystem.h
  5. +8 −27 folve-main.cc
  6. +8 −0 sound-processor.h
  7. +13 −4 status-server.cc
View
1 README
@@ -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.
View
7 file-handler.h
@@ -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.
@@ -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
@@ -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_;
View
105 folve-filesystem.cc
@@ -16,6 +16,7 @@
#include "folve-filesystem.h"
#include <FLAC/metadata.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sndfile.h>
@@ -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;
}
@@ -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);
}
@@ -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),
@@ -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) {
@@ -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) {
@@ -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_;
@@ -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],
@@ -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_;
@@ -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;
View
20 folve-filesystem.h
@@ -21,6 +21,7 @@
#include <string>
#include <vector>
+#include <set>
#include "file-handler-cache.h"
#include "file-handler.h"
@@ -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.
@@ -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_; }
@@ -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_;
View
35 folve-main.cc
@@ -40,16 +40,6 @@ static struct FolveRuntime {
int status_port;
} folve_rt;
-static int has_suffix_string (const char *str, const char *suffix) {
- if (!str || !suffix)
- return 0;
- size_t str_len = strlen(str);
- size_t suffix_len = strlen(suffix);
- if (suffix_len > str_len)
- return 0;
- return strncmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
-}
-
static char *concat_path(char *buf, const char *a, const char *b) {
strcpy(buf, a);
strcat(buf, b);
@@ -58,14 +48,8 @@ static char *concat_path(char *buf, const char *a, const char *b) {
// Given a relative path from the root of the mounted file-system, get the
// original file from the source filesystem.
-// TODO(hzeller): move the ogg.fuse.flac logic into convolver-filesystem.
static const char *assemble_orig_path(char *buf, const char *path) {
- char *result = concat_path(buf, folve_rt.fs->underlying_dir().c_str(), path);
- static const char magic_ogg_rewrite[] = ".ogg.fuse.flac";
- if (has_suffix_string(result, magic_ogg_rewrite)) {
- *(result + strlen(result) - strlen(".fuse.flac")) = '\0';
- }
- return result;
+ return concat_path(buf, folve_rt.fs->underlying_dir().c_str(), path);
}
// Essentially lstat(). Just forward to the original filesystem (this
@@ -96,19 +80,12 @@ static int folve_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
if (dp == NULL)
return -errno;
- char ogg_rewrite_buffer[PATH_MAX];
-
while ((de = readdir(dp)) != NULL) {
struct stat st;
memset(&st, 0, sizeof(st));
st.st_ino = de->d_ino;
st.st_mode = de->d_type << 12;
const char *entry_name = de->d_name;
- if (has_suffix_string(entry_name, ".ogg")) {
- // For ogg files, we pretend they actually end with 'flac', because
- // we transparently rewrite them.
- entry_name = concat_path(ogg_rewrite_buffer, entry_name, ".fuse.flac");
- }
if (filler(buf, entry_name, &st, 0))
break;
}
@@ -139,9 +116,7 @@ static int folve_open(const char *path, struct fuse_file_info *fi) {
// The file-handle has the neat property to be 64 bit - so we can actually
// stuff a pointer to our file handler object in there :)
// (Yay, someone was thinking while developing that API).
- char path_buf[PATH_MAX];
- const char *orig_path = assemble_orig_path(path_buf, path);
- FileHandler *handler = folve_rt.fs->CreateHandler(path, orig_path);
+ FileHandler *handler = folve_rt.fs->GetOrCreateHandler(path);
if (handler == NULL)
return -errno;
fi->fh = (uint64_t) handler;
@@ -212,6 +187,7 @@ static int usage(const char *prg) {
"\t you'll get a drop-down select on the HTTP "
"status page.\n"
"\t-p <port> : Port to run the HTTP status server on.\n"
+ "\t-g : 'gapless': convolve adjacent files (experimental).\n"
"\t-D : Moderate volume Folve debug messages to syslog.\n"
"\t Can then also be toggled in the UI.\n"
"\t-f : Operate in foreground; useful for debugging.\n"
@@ -232,6 +208,7 @@ enum {
FOLVE_OPT_PORT = 42,
FOLVE_OPT_CONFIG,
FOLVE_OPT_DEBUG,
+ FOLVE_OPT_GAPLESS,
};
int FolveOptionHandling(void *data, const char *arg, int key,
@@ -257,6 +234,9 @@ int FolveOptionHandling(void *data, const char *arg, int key,
rt->fs->set_debug_ui_enabled(true);
rt->fs->SetDebugMode(true);
return 0;
+ case FOLVE_OPT_GAPLESS:
+ rt->fs->set_gapless_processing(true);
+ return 0;
}
return 1;
}
@@ -273,6 +253,7 @@ int main(int argc, char *argv[]) {
FUSE_OPT_KEY("-p ", FOLVE_OPT_PORT),
FUSE_OPT_KEY("-c ", FOLVE_OPT_CONFIG),
FUSE_OPT_KEY("-D", FOLVE_OPT_DEBUG),
+ FUSE_OPT_KEY("-g", FOLVE_OPT_GAPLESS),
FUSE_OPT_END
};
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
View
8 sound-processor.h
@@ -39,6 +39,12 @@ class SoundProcessor {
return zita_config_.fragm == input_pos_;
}
+ // Number of samples that are pending. Typically once we have this passed
+ // over to a new file.
+ int pending_writes() const {
+ return output_pos_ >= 0 ? zita_config_.fragm - output_pos_ : 0;
+ }
+
// Write number of processed samples out to given soundfile. Processes
// the data first if necessary. assert(), that there is at least 1 sample
// to process.
@@ -57,6 +63,8 @@ class SoundProcessor {
const ZitaConfig zita_config_;
float *const buffer_;
const int channels_;
+ // TODO: instead of two positions, better have one position and two states
+ // READ, WRITE
int input_pos_;
int output_pos_; // written position. -1, if not processed yet.
float max_out_value_observed_;
View
17 status-server.cc
@@ -133,13 +133,15 @@ void StatusServer::RetireHandlerEvent(FileHandler *handler) {
}
static const char sMessageRowHtml[] =
- "<td>%s</td><td style='font-size:small;'>%s</td>"
+ "<td>%s</td><td colspan='3' style='font-size:small;'>%s</td>"
"<td colspan='3' align='center'>-</td>";
static const char sProgressRowHtml[] =
- "<td>%s</td><td>"
- "<div style='width:%dpx; border:1px solid black;'>\n"
+ "<td>%s</td>"
+ "<td>%s</td>" // gapless marker
+ "<td><div style='width:%dpx; border:1px solid black;'>\n"
" <div style='width:%d%%;background:%s;'>&nbsp;</div>\n</div></td>"
+ "<td>%s</td>" // gapless
"<td align='right'>%2d:%02d</td><td>/</td><td align='right'>%2d:%02d</td>";
static void AppendFileInfo(std::string *result, const char *progress_style,
@@ -160,7 +162,9 @@ static void AppendFileInfo(std::string *result, const char *progress_style,
const int secs = stats.duration_seconds;
const int fract_sec = stats.progress * secs;
Appendf(result, sProgressRowHtml, status,
+ stats.in_gapless ? "&rarr;" : "",
kProgressWidth, (int) (100 * stats.progress), progress_style,
+ stats.out_gapless ? "&rarr;" : "",
fract_sec / 60, fract_sec % 60, secs / 60, secs % 60);
}
Appendf(result, "<td bgcolor='#c0c0c0'>&nbsp;%s&nbsp;</td>",
@@ -256,8 +260,13 @@ const std::string &StatusServer::CreatePage() {
Appendf(&content_, "<h3>Accessed Recently</h3>\n%zd in recency cache\n",
stat_list.size());
+ if (filesystem_->gapless_processing()) {
+ content_.append("<br/>'&rarr;' denote gapless transfers\n");
+ }
content_.append("<table>\n");
- Appendf(&content_, "<tr><th>Stat</th><th width='%dpx'>Progress</th>"
+ Appendf(&content_, "<tr><th>Stat</th><td><!--gapless in--></td>"
+ "<th width='%dpx'>Progress</th>" // progress bar.
+ "<td><!-- gapless out --></td>"
"<th>Pos</th><td></td><th>Len</th><th>Format</th>"
"<th align='left'>File</th></tr>\n", kProgressWidth);
CompareStats comparator;

0 comments on commit 08bbf28

Please sign in to comment.
Something went wrong with that request. Please try again.