Skip to content

Commit

Permalink
Merge pull request #31 from robertyseward/master
Browse files Browse the repository at this point in the history
Add VBR encoding support and stat cache
  • Loading branch information
khenriks committed May 17, 2015
2 parents af853b8 + ef8de73 commit 0f1cff9
Show file tree
Hide file tree
Showing 13 changed files with 470 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/Makefile.am
Expand Up @@ -2,7 +2,7 @@ WARNINGS = -Wall -Wextra -Wconversion -Wno-sign-conversion
AM_CFLAGS = -std=gnu99 $(fuse_CFLAGS) $(WARNINGS)
AM_CXXFLAGS = -std=c++98 $(fuse_CFLAGS) $(WARNINGS)
bin_PROGRAMS = mp3fs
mp3fs_SOURCES = mp3fs.c fuseops.c transcode.cc transcode.h buffer.cc buffer.h coders.cc coders.h
mp3fs_SOURCES = mp3fs.c fuseops.c transcode.cc transcode.h buffer.cc buffer.h coders.cc coders.h stats_cache.cc stats_cache.h
mp3fs_LDADD = $(fuse_LIBS)
if HAVE_FLAC
mp3fs_SOURCES += flac_decoder.cc flac_decoder.h
Expand Down
4 changes: 2 additions & 2 deletions src/coders.cc
Expand Up @@ -34,9 +34,9 @@
#endif

/* Create instance of class derived from Encoder. */
Encoder* Encoder::CreateEncoder(std::string file_type) {
Encoder* Encoder::CreateEncoder(std::string file_type, size_t actual_size) {
#ifdef HAVE_MP3
if (file_type == "mp3") return new Mp3Encoder();
if (file_type == "mp3") return new Mp3Encoder(actual_size);
#endif
return NULL;
}
Expand Down
6 changes: 5 additions & 1 deletion src/coders.h
Expand Up @@ -69,12 +69,14 @@ class Encoder {
int data_length) = 0;
virtual void set_gain_db(const double dbgain) = 0;
virtual int render_tag(Buffer& buffer) = 0;
virtual size_t get_actual_size() const = 0;
virtual size_t calculate_size() const = 0;
virtual int encode_pcm_data(const int32_t* const data[], int numsamples,
int sample_size, Buffer& buffer) = 0;
virtual int encode_finish(Buffer& buffer) = 0;

static Encoder* CreateEncoder(const std::string file_type);
static Encoder* CreateEncoder(const std::string file_type,
size_t actual_size = 0);
};

/* Decoder class interface */
Expand All @@ -83,6 +85,8 @@ class Decoder {
virtual ~Decoder() { };

virtual int open_file(const char* filename) = 0;
/* The modified time of the decoder file */
virtual time_t mtime() = 0;
virtual int process_metadata(Encoder* encoder) = 0;
virtual int process_single_fr(Encoder* encoder, Buffer* buffer) = 0;

Expand Down
28 changes: 27 additions & 1 deletion src/flac_decoder.cc
Expand Up @@ -43,9 +43,31 @@ int FlacDecoder::open_file(const char* filename) {

mp3fs_debug("FLAC ready to initialize.");

int fd = open(filename, 0);
if (fd < 0) {
mp3fs_debug("FLAC open failed.");
return -1;
}

struct stat s;
if (fstat(fd, &s) < 0) {
mp3fs_debug("FLAC stat failed.");
close(fd);
return -1;
}
mtime_ = s.st_mtime;

FILE *file = fdopen(fd, "r");
if (file == 0) {
mp3fs_debug("FLAC fdopen failed.");
close(fd);
return -1;
}

/* Initialise decoder */
if (init(filename) != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
if (init(file) != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
mp3fs_debug("FLAC init failed.");
fclose(file);
return -1;
}

Expand All @@ -54,6 +76,10 @@ int FlacDecoder::open_file(const char* filename) {
return 0;
}

time_t FlacDecoder::mtime() {
return mtime_;
}

/*
* Process the metadata in the FLAC file. This should be called at the
* beginning, before reading audio data. The set_text_tag() and
Expand Down
2 changes: 2 additions & 0 deletions src/flac_decoder.h
Expand Up @@ -32,6 +32,7 @@
class FlacDecoder : public Decoder, private FLAC::Decoder::File {
public:
int open_file(const char* filename);
time_t mtime();
int process_metadata(Encoder* encoder);
int process_single_fr(Encoder* encoder, Buffer* buffer);
protected:
Expand All @@ -42,6 +43,7 @@ class FlacDecoder : public Decoder, private FLAC::Decoder::File {
private:
Encoder* encoder_c;
Buffer* buffer_c;
time_t mtime_;
FLAC::Metadata::StreamInfo info;
typedef std::map<std::string,int> meta_map_t;
static const meta_map_t create_meta_map();
Expand Down
4 changes: 1 addition & 3 deletions src/fuseops.c
Expand Up @@ -215,7 +215,6 @@ static int mp3fs_getattr(const char *path, struct stat *stbuf) {
stbuf->st_size = transcoder_get_size(trans);
stbuf->st_blocks = (stbuf->st_size + 512 - 1) / 512;

transcoder_finish(trans);
transcoder_delete(trans);
}

Expand Down Expand Up @@ -319,7 +318,7 @@ static int mp3fs_read(const char *path, char *buf, size_t size, off_t offset,
open_fail:
free(origpath);
translate_fail:
if (read) {
if (read >= 0) {
return (int)read;
} else {
return -errno;
Expand Down Expand Up @@ -363,7 +362,6 @@ static int mp3fs_release(const char *path, struct fuse_file_info *fi) {

trans = (struct transcoder*)fi->fh;
if (trans) {
transcoder_finish(trans);
transcoder_delete(trans);
}

Expand Down
60 changes: 53 additions & 7 deletions src/mp3_encoder.cc
Expand Up @@ -29,6 +29,9 @@

#include "transcode.h"

/* Copied from lame */
#define MAX_VBR_FRAME_SIZE 2880

/* Keep these items in static scope. */
namespace {

Expand Down Expand Up @@ -62,7 +65,7 @@ static void lame_debug(const char *fmt, va_list list) {
* particular file. Currently error handling is poor. If we run out
* of memory, these routines will fail silently.
*/
Mp3Encoder::Mp3Encoder() {
Mp3Encoder::Mp3Encoder(size_t _actual_size) : actual_size(_actual_size) {
id3tag = id3_tag_new();

mp3fs_debug("LAME ready to initialize.");
Expand All @@ -72,9 +75,16 @@ Mp3Encoder::Mp3Encoder() {
set_text_tag(METATAG_ENCODER, PACKAGE_NAME);

/* Set lame parameters. */
lame_set_quality(lame_encoder, params.quality);
lame_set_brate(lame_encoder, params.bitrate);
lame_set_bWriteVbrTag(lame_encoder, 0);
if (params.vbr) {
lame_set_VBR(lame_encoder, vbr_mt);
lame_set_VBR_q(lame_encoder, params.quality);
lame_set_VBR_max_bitrate_kbps(lame_encoder, params.bitrate);
lame_set_bWriteVbrTag(lame_encoder, 1);
} else {
lame_set_quality(lame_encoder, params.quality);
lame_set_brate(lame_encoder, params.bitrate);
lame_set_bWriteVbrTag(lame_encoder, 0);
}
lame_set_errorf(lame_encoder, &lame_error);
lame_set_msgf(lame_encoder, &lame_msg);
lame_set_debugf(lame_encoder, &lame_debug);
Expand Down Expand Up @@ -252,21 +262,40 @@ int Mp3Encoder::render_tag(Buffer& buffer) {
id3_tag_options(id3tag, ID3_TAG_OPTION_ID3V1, ~0);
write_ptr = buffer.write_prepare(id3v1_tag_length,
calculate_size() - id3v1_tag_length);
if (!write_ptr) {
return -1;
}
id3_tag_render(id3tag, write_ptr);

return 0;
}

/*
* Get the actual number of bytes in the encoded file, i.e. without any
* padding. Valid only after encode_finish() has been called.
*/
size_t Mp3Encoder::get_actual_size() const {
return actual_size;
}

/*
* Properly calculate final file size. This is the sum of the size of
* ID3v2, ID3v1, and raw MP3 data. This is theoretically only approximate
* but in practice gives excellent answers, usually exactly correct.
* Cast to 64-bit int to avoid overflow.
*/
size_t Mp3Encoder::calculate_size() const {
return id3size + id3v1_tag_length
+ (uint64_t)lame_get_totalframes(lame_encoder)*144*params.bitrate*10
/ (lame_get_out_samplerate(lame_encoder)/100);
if (actual_size != 0) {
return actual_size;
} else if (params.vbr) {
return id3size + id3v1_tag_length + MAX_VBR_FRAME_SIZE
+ (uint64_t)lame_get_totalframes(lame_encoder)*144*params.bitrate*10
/ (lame_get_in_samplerate(lame_encoder)/100);
} else {
return id3size + id3v1_tag_length
+ (uint64_t)lame_get_totalframes(lame_encoder)*144*params.bitrate*10
/ (lame_get_out_samplerate(lame_encoder)/100);
}
}

/*
Expand Down Expand Up @@ -333,6 +362,23 @@ int Mp3Encoder::encode_finish(Buffer& buffer) {
}

buffer.increment_pos(len);
actual_size = buffer.tell() + id3v1_tag_length;

/*
* Write the VBR tag data at id3size bytes after the beginning. lame
* already put dummy bytes here when lame_init_params() was called.
*/
if (params.vbr) {
uint8_t* write_ptr = buffer.write_prepare(MAX_VBR_FRAME_SIZE, id3size);
if (!write_ptr) {
return -1;
}
size_t vbr_tag_size = lame_get_lametag_frame(lame_encoder, write_ptr,
MAX_VBR_FRAME_SIZE);
if (vbr_tag_size > MAX_VBR_FRAME_SIZE) {
return -1;
}
}

return len;
}
Expand Down
5 changes: 4 additions & 1 deletion src/mp3_encoder.h
Expand Up @@ -32,7 +32,7 @@ class Mp3Encoder : public Encoder {
public:
static const size_t id3v1_tag_length = 128;

Mp3Encoder();
Mp3Encoder(size_t actual_size);
~Mp3Encoder();

int set_stream_params(uint64_t num_samples, int sample_rate,
Expand All @@ -43,12 +43,15 @@ class Mp3Encoder : public Encoder {
int data_length);
void set_gain_db(const double dbgain);
int render_tag(Buffer& buffer);
size_t get_actual_size() const;
size_t calculate_size() const;
int encode_pcm_data(const int32_t* const data[], int numsamples,
int sample_size, Buffer& buffer);
int encode_finish(Buffer& buffer);

private:
lame_t lame_encoder;
size_t actual_size; // Use this as the size instead of computing it.
struct id3_tag* id3tag;
size_t id3size;
typedef std::map<int,const char*> meta_map_t;
Expand Down
83 changes: 50 additions & 33 deletions src/mp3fs.c
Expand Up @@ -37,12 +37,14 @@
#endif

struct mp3fs_params params = {
.basepath = NULL,
.bitrate = 128,
.quality = 5,
.debug = 0,
.gainmode = 1,
.gainref = 89.0,
.basepath = NULL,
.bitrate = 128,
.quality = 5,
.vbr = 0,
.statcachesize = 0,
.debug = 0,
.gainmode = 1,
.gainref = 89.0,
#ifdef HAVE_MP3
.desttype = "mp3",
#endif
Expand All @@ -57,25 +59,29 @@ enum {
#define MP3FS_OPT(t, p, v) { t, offsetof(struct mp3fs_params, p), v }

static struct fuse_opt mp3fs_opts[] = {
MP3FS_OPT("--quality=%u", quality, 0),
MP3FS_OPT("quality=%u", quality, 0),
MP3FS_OPT("-d", debug, 1),
MP3FS_OPT("debug", debug, 1),
MP3FS_OPT("-b %u", bitrate, 0),
MP3FS_OPT("bitrate=%u", bitrate, 0),
MP3FS_OPT("--gainmode=%d", gainmode, 0),
MP3FS_OPT("gainmode=%d", gainmode, 0),
MP3FS_OPT("--gainref=%f", gainref, 0),
MP3FS_OPT("gainref=%f", gainref, 0),
MP3FS_OPT("--desttype=%s", desttype, 0),
MP3FS_OPT("desttype=%s", desttype, 0),

FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_KEY("-d", KEY_KEEP_OPT),
FUSE_OPT_KEY("debug", KEY_KEEP_OPT),
MP3FS_OPT("--quality=%u", quality, 0),
MP3FS_OPT("quality=%u", quality, 0),
MP3FS_OPT("-d", debug, 1),
MP3FS_OPT("debug", debug, 1),
MP3FS_OPT("-b %u", bitrate, 0),
MP3FS_OPT("bitrate=%u", bitrate, 0),
MP3FS_OPT("--vbr", vbr, 1),
MP3FS_OPT("vbr", vbr, 1),
MP3FS_OPT("--statcachesize=%u", statcachesize, 0),
MP3FS_OPT("statcachesize=%u", statcachesize, 1),
MP3FS_OPT("--gainmode=%d", gainmode, 0),
MP3FS_OPT("gainmode=%d", gainmode, 0),
MP3FS_OPT("--gainref=%f", gainref, 0),
MP3FS_OPT("gainref=%f", gainref, 0),
MP3FS_OPT("--desttype=%s", desttype, 0),
MP3FS_OPT("desttype=%s", desttype, 0),

FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_KEY("-d", KEY_KEEP_OPT),
FUSE_OPT_KEY("debug", KEY_KEEP_OPT),
FUSE_OPT_END
};

Expand All @@ -92,6 +98,14 @@ Encoding options:\n\
encoding bitrate: Acceptable values for RATE\n\
include 96, 112, 128, 160, 192, 224, 256, and\n\
320; 128 is the default\n\
--vbr, -ovbr Use variable bit rate encoding. When set, the\n\
bit rate set with '-b' sets the maximum bit rate.\n\
Performance will be terrible unless the\n\
statcachesize is enabled.\n\
--statcachesize=SIZE, -ostatcachesize=SIZE\n\
Set the number of entries for the file stats\n\
cache. Necessary for decent performance when\n\
VBR is enabled. Each entry takes 100-200 bytes.\n\
--gainmode=<0,1,2>, -ogainmode=<0,1,2>\n\
what to do with ReplayGain tags:\n\
0 - ignore, 1 - prefer album gain (default),\n\
Expand Down Expand Up @@ -193,15 +207,18 @@ int main(int argc, char *argv[]) {
openlog("mp3fs", params.debug ? LOG_PERROR : 0, LOG_USER);

mp3fs_debug("MP3FS options:\n"
"basepath: %s\n"
"bitrate: %u\n"
"quality: %u\n"
"gainmode: %d\n"
"gainref: %f\n"
"desttype: %s\n"
"basepath: %s\n"
"bitrate: %u\n"
"quality: %u\n"
"vbr: %u\n"
"statcachesize: %u\n"
"gainmode: %d\n"
"gainref: %f\n"
"desttype: %s\n"
"\n",
params.basepath, params.bitrate, params.quality,
params.gainmode, params.gainref, params.desttype);
params.basepath, params.bitrate, params.quality, params.vbr,
params.statcachesize, params.gainmode, params.gainref,
params.desttype);

// start FUSE
ret = fuse_main(args.argc, args.argv, &mp3fs_ops, NULL);
Expand Down

0 comments on commit 0f1cff9

Please sign in to comment.