diff options
author | Alexey Yakovenko <wakeroid@gmail.com> | 2009-09-16 21:27:08 +0200 |
---|---|---|
committer | Alexey Yakovenko <wakeroid@gmail.com> | 2009-09-16 21:27:08 +0200 |
commit | 6d505b25848c8db7363e54a6902aebc859f53bc7 (patch) | |
tree | ea8bd4b5d748f61c838214de550997d734c95aec | |
parent | 743cf8e3a236a18080de2fb9467185d54e60afe4 (diff) |
gapless playback 2nd iteration - sample-accurate seeking on flac and ape
-rw-r--r-- | deadbeef.h | 12 | ||||
-rw-r--r-- | playlist.c | 47 | ||||
-rw-r--r-- | playlist.h | 6 | ||||
-rw-r--r-- | plugins.c | 4 | ||||
-rw-r--r-- | plugins/ffap/ffap.c | 46 | ||||
-rw-r--r-- | plugins/flac/flac.c | 82 | ||||
-rw-r--r-- | plugins/mpgmad/mpgmad.c | 3 | ||||
-rw-r--r-- | plugins/vorbis/vorbis.c | 2 | ||||
-rw-r--r-- | streamer.c | 4 |
9 files changed, 114 insertions, 92 deletions
@@ -54,7 +54,7 @@ extern "C" { // DON'T release plugins without DB_PLUGIN_SET_API_VERSION #define DB_API_VERSION_MAJOR 0 -#define DB_API_VERSION_MINOR 1 +#define DB_API_VERSION_MINOR 2 #define DB_PLUGIN_SET_API_VERSION\ .plugin.api_vmajor = DB_API_VERSION_MAJOR,\ @@ -64,11 +64,13 @@ extern "C" { // playlist structures // playlist item -// there are "public" fields, available to plugins +// these are "public" fields, available to plugins typedef struct { char *fname; // full pathname struct DB_decoder_s *decoder; // codec to use with this file int tracknum; // used for stuff like sid, nsf, cue (will be ignored by most codecs) + int startsample; + int endsample; float timestart; // start time of cue track, or -1 float timeend; // end time of cue track, or -1 float duration; // in seconds @@ -164,8 +166,8 @@ typedef struct { void (*pl_add_meta) (DB_playItem_t *it, const char *key, const char *value); const char *(*pl_find_meta) (DB_playItem_t *song, const char *meta); // cuesheet support - DB_playItem_t *(*pl_insert_cue_from_buffer) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, float duration); - DB_playItem_t * (*pl_insert_cue) (DB_playItem_t *after, const char *filename, struct DB_decoder_s *decoder, const char *ftype, float duration); + DB_playItem_t *(*pl_insert_cue_from_buffer) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate); + DB_playItem_t * (*pl_insert_cue) (DB_playItem_t *after, const char *filename, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate); // volume control void (*volume_set_db) (float dB); float (*volume_get_db) (void); @@ -239,7 +241,7 @@ typedef struct DB_decoder_s { // perform seeking in samples (if possible) // return -1 if failed, or 0 on success // if -1 is returned, that will mean that streamer must skip that song - int (*seek_sample) (int64_t samples); + int (*seek_sample) (int sample); // 'insert' is called to insert new item to playlist // decoder is responsible to calculate duration, split it into subsongs, load cuesheet, etc @@ -35,12 +35,8 @@ #include "plugins.h" #include "junklib.h" -//#include "cvorbis.h" -//#include "cdumb.h" -//#include "cmp3.h" -//#include "cgme.h" -//#include "cflac.h" -//#include "csid.h" +#define trace(...) { fprintf(stderr, __VA_ARGS__); } +//#define trace(fmt,...) #define SKIP_BLANK_CUE_TRACKS 1 @@ -146,7 +142,7 @@ pl_cue_parse_time (const char *p) { } static playItem_t * -pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, char *track, char *index00, char *index01, char *pregap, char *title, char *performer, char *albumtitle, struct DB_decoder_s *decoder, const char *ftype) { +pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, char *track, char *index00, char *index01, char *pregap, char *title, char *performer, char *albumtitle, struct DB_decoder_s *decoder, const char *ftype, int samplerate) { if (!track[0]) { return after; } @@ -181,6 +177,11 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c // no pregap (*prev)->timeend = f_index01; } + else { + return after; + } + (*prev)->endsample = ((*prev)->timeend * samplerate) - 1; + trace ("calc endsample=%d, timeend=%f, samplerate=%d\n", (*prev)->endsample, (*prev)->timeend, samplerate); (*prev)->duration = (*prev)->timeend - (*prev)->timestart; } // non-compliant hack to handle tracks which only store pregap info @@ -199,6 +200,7 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c else { it->timestart = f_index00; } + it->startsample = it->timestart * samplerate; it->timeend = -1; // will be filled by next read, or by decoder it->filetype = ftype; @@ -212,7 +214,8 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c } playItem_t * -pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, float duration) { +pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate) { + trace ("pl_insert_cue_from_buffer numsamples=%d, samplerate=%d\n", numsamples, samplerate); char performer[256] = ""; char albumtitle[256] = ""; char track[256] = ""; @@ -258,7 +261,7 @@ pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t * } else if (!strncmp (p, "TRACK ", 6)) { // add previous track - after = pl_process_cue_track (after, fname, &prev, track, index00, index01, pregap, title, performer, albumtitle, decoder, ftype); + after = pl_process_cue_track (after, fname, &prev, track, index00, index01, pregap, title, performer, albumtitle, decoder, ftype, samplerate); track[0] = 0; title[0] = 0; pregap[0] = 0; @@ -284,16 +287,18 @@ pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t * // fprintf (stderr, "got unknown line:\n%s\n", p); } } - after = pl_process_cue_track (after, fname, &prev, track, index00, index01, pregap, title, performer, albumtitle, decoder, ftype); + after = pl_process_cue_track (after, fname, &prev, track, index00, index01, pregap, title, performer, albumtitle, decoder, ftype, samplerate); if (after) { - after->timeend = duration; + after->endsample = numsamples-1; + after->timeend = (float)numsamples/samplerate; after->duration = after->timeend - after->timestart; } return after; } playItem_t * -pl_insert_cue (playItem_t *after, const char *fname, struct DB_decoder_s *decoder, const char *ftype, float duration) { +pl_insert_cue (playItem_t *after, const char *fname, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate) { + trace ("pl_insert_cue numsamples=%d, samplerate=%d\n", numsamples, samplerate); int len = strlen (fname); char cuename[len+5]; strcpy (cuename, fname); @@ -323,7 +328,7 @@ pl_insert_cue (playItem_t *after, const char *fname, struct DB_decoder_s *decode return NULL; } fclose (fp); - return pl_insert_cue_from_buffer (after, fname, buf, sz, decoder, ftype, duration); + return pl_insert_cue_from_buffer (after, fname, buf, sz, decoder, ftype, numsamples, samplerate); } playItem_t * @@ -590,6 +595,8 @@ pl_item_copy (playItem_t *out, playItem_t *it) { out->fname = strdup (it->fname); out->decoder = it->decoder; out->tracknum = it->tracknum; + out->startsample = it->startsample; + out->endsample = it->endsample; out->timestart = it->timestart; out->timeend = it->timeend; out->duration = it->duration; @@ -1027,6 +1034,12 @@ pl_save (const char *fname) { if (fwrite (&l, 1, 2, fp) != 2) { goto save_fail; } + if (fwrite (&it->startsample, 1, 4, fp) != 4) { + goto save_fail; + } + if (fwrite (&it->endsample, 1, 4, fp) != 4) { + goto save_fail; + } if (fwrite (&it->timestart, 1, 4, fp) != 4) { goto save_fail; } @@ -1158,6 +1171,14 @@ pl_load (const char *fname) { goto load_fail; } it->tracknum = l; + // startsample + if (fread (&it->startsample, 1, 4, fp) != 4) { + goto load_fail; + } + // endsample + if (fread (&it->endsample, 1, 4, fp) != 4) { + goto load_fail; + } // timestart if (fread (&it->timestart, 1, 4, fp) != 4) { goto load_fail; @@ -35,6 +35,8 @@ typedef struct playItem_s { char *fname; // full pathname struct DB_decoder_s *decoder; // codec to use with this file int tracknum; // used for stuff like sid, nsf, cue (will be ignored by most codecs) + int startsample; + int endsample; float timestart; // start time of cue track, or -1 float timeend; // end time of cue track, or -1 float duration; // in seconds @@ -103,10 +105,10 @@ int pl_get_idx_of (playItem_t *it); playItem_t * -pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, float duration); +pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate); playItem_t * -pl_insert_cue (playItem_t *after, const char *cuename, struct DB_decoder_s *decoder, const char *ftype, float duration); +pl_insert_cue (playItem_t *after, const char *cuename, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate); //int //pl_set_current (playItem_t *it); @@ -85,8 +85,8 @@ static DB_functions_t deadbeef_api = { .pl_add_meta = (void (*) (DB_playItem_t *, const char *, const char *))pl_add_meta, .pl_find_meta = (const char *(*) (DB_playItem_t *, const char *))pl_find_meta, // cuesheet support - .pl_insert_cue_from_buffer = (DB_playItem_t *(*) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, float duration))pl_insert_cue_from_buffer, - .pl_insert_cue = (DB_playItem_t *(*)(DB_playItem_t *, const char *, struct DB_decoder_s *, const char *ftype, float duration))pl_insert_cue, + .pl_insert_cue_from_buffer = (DB_playItem_t *(*) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate))pl_insert_cue_from_buffer, + .pl_insert_cue = (DB_playItem_t *(*)(DB_playItem_t *, const char *, struct DB_decoder_s *, const char *ftype, int numsamples, int samplerate))pl_insert_cue, // volume control .volume_set_db = plug_volume_set_db, .volume_get_db = volume_get_db, diff --git a/plugins/ffap/ffap.c b/plugins/ffap/ffap.c index 8e7d0748..f995f9b2 100644 --- a/plugins/ffap/ffap.c +++ b/plugins/ffap/ffap.c @@ -38,11 +38,16 @@ #define ENABLE_DEBUG 0 +#define trace(...) { fprintf(stderr, __VA_ARGS__); } +//#define trace(fmt,...) + static DB_decoder_t plugin; static DB_functions_t *deadbeef; -float timestart; -float timeend; +static float timestart; +static float timeend; +static int startsample; +static int endsample; #define PACKET_BUFFER_SIZE 100000 @@ -695,13 +700,18 @@ ffap_init(DB_playItem_t *it) plugin.info.channels = ape_ctx.channels; plugin.info.readpos = 0; if (it->timeend > 0) { + startsample = it->startsample; + endsample = it->endsample; timestart = it->timestart; timeend = it->timeend; - plugin.seek (0); + plugin.seek_sample (0); + trace ("start: %d/%f, end: %d/%f\n", startsample, timestart, endsample, timeend); } else { timestart = 0; timeend = it->duration; + startsample = 0; + endsample = ape_ctx.totalsamples-1; } return 0; } @@ -1632,7 +1642,7 @@ ffap_insert (DB_playItem_t *after, const char *fname) { float duration = ape_ctx.totalsamples / (float)ape_ctx.samplerate; DB_playItem_t *it; - it = deadbeef->pl_insert_cue (after, fname, &plugin, "APE", duration); + it = deadbeef->pl_insert_cue (after, fname, &plugin, "APE", ape_ctx.totalsamples, ape_ctx.samplerate); if (it) { fclose (fp); ape_free_ctx (&ape_ctx); @@ -1707,6 +1717,31 @@ ffap_read_int16 (char *buffer, int size) { } static int +ffap_seek_sample (int sample) { + sample += startsample; + trace ("seeking to %d/%d\n", sample, ape_ctx.totalsamples); + uint32_t newsample = sample; + if (newsample > ape_ctx.totalsamples) { + trace ("eof\n"); + return -1; + } + int nframe = newsample / ape_ctx.blocksperframe; + if (nframe >= ape_ctx.totalframes) { + trace ("eof2\n"); + return -1; + } + ape_ctx.currentframe = nframe; + ape_ctx.samplestoskip = newsample - nframe * ape_ctx.blocksperframe; + + // reset decoder + ape_ctx.packet_remaining = 0; + ape_ctx.samples = 0; + ape_ctx.currentsample = newsample; + plugin.info.readpos = (float)newsample/ape_ctx.samplerate-timestart; + return 0; +} + +static int ffap_seek (float seconds) { seconds += timestart; uint32_t newsample = seconds * plugin.info.samplerate; @@ -1724,7 +1759,7 @@ ffap_seek (float seconds) { ape_ctx.packet_remaining = 0; ape_ctx.samples = 0; ape_ctx.currentsample = newsample; - plugin.info.readpos = seconds - timestart; + plugin.info.readpos = (float)newsample / ape_ctx.samplerate - timestart; return 0; } @@ -1745,6 +1780,7 @@ static DB_decoder_t plugin = { .free = ffap_free, .read_int16 = ffap_read_int16, .seek = ffap_seek, + .seek_sample = ffap_seek_sample, .insert = ffap_insert, .exts = exts, .id = "ffap", diff --git a/plugins/flac/flac.c b/plugins/flac/flac.c index 5188947e..b7d0277e 100644 --- a/plugins/flac/flac.c +++ b/plugins/flac/flac.c @@ -33,6 +33,8 @@ static char buffer[BUFFERSIZE]; // this buffer always has int32 samples static int remaining; // bytes remaining in buffer from last read static float timestart; static float timeend; +static int startsample; +static int endsample; static FLAC__StreamDecoderWriteStatus cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const inputbuffer[], void *client_data) { @@ -142,8 +144,10 @@ cflac_init (DB_playItem_t *it) { } timestart = it->timestart; timeend = it->timeend; + startsample = it->startsample; + endsample = it->endsample; if (timeend > timestart || timeend < 0) { - plugin.seek (0); + plugin.seek_sample (0); } plugin.info.readpos = 0; @@ -256,6 +260,17 @@ cflac_seek (float time) { return 0; } +static int +cflac_seek_sample (int sample) { + sample += startsample; + if (!FLAC__stream_decoder_seek_absolute (decoder, (FLAC__uint64)(sample))) { + return -1; + } + remaining = 0; + plugin.info.readpos = (float)sample / plugin.info.samplerate - timestart; + return 0; +} + static FLAC__StreamDecoderWriteStatus cflac_init_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const inputbuffer[], void *client_data) { if (frame->header.blocksize == 0 || cflac_init_stop_decoding) { @@ -271,6 +286,7 @@ typedef struct { int samplerate; int nchannels; float duration; + int totalsamples; } cue_cb_data_t; static void @@ -283,65 +299,8 @@ cflac_init_cue_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC_ cb->samplerate = metadata->data.stream_info.sample_rate; cb->nchannels = metadata->data.stream_info.channels; cb->duration = metadata->data.stream_info.total_samples / (float)metadata->data.stream_info.sample_rate; + cb->totalsamples = metadata->data.stream_info.total_samples; } - // {{{ disabled -#if 0 - else if (metadata->type == FLAC__METADATA_TYPE_CUESHEET) { - //printf ("loading embedded cuesheet!\n"); - const FLAC__StreamMetadata_CueSheet *cue = &metadata->data.cue_sheet; - DB_playItem_t *prev = NULL; - for (int i = 0; i < cue->num_tracks; i++) { - FLAC__StreamMetadata_CueSheet_Track *t = &cue->tracks[i]; - if (t->type == 0 && t->num_indices > 0) { - FLAC__StreamMetadata_CueSheet_Index *idx = t->indices; - DB_playItem_t *it = malloc (sizeof (DB_playItem_t)); - memset (it, 0, sizeof (DB_playItem_t)); - it->decoder = &plugin; - it->fname = strdup (cb->fname); - it->tracknum = t->number; - it->timestart = (float)t->offset / cb->samplerate; - it->timeend = -1; // will be filled by next read - if (prev) { - prev->timeend = it->timestart; - prev->duration = prev->timeend - prev->timestart; - } - it->filetype = "FLAC"; - //printf ("N: %d, t: %f, bps=%d\n", it->tracknum, it->timestart/60.f, cb->samplerate); - DB_playItem_t *ins = deadbeef->pl_insert_item (cb->last, it); - if (ins) { - cb->last = ins; - } - else { - deadbeef->pl_item_free (it); - it = NULL; - } - prev = it; - } - } - if (prev) { - prev->timeend = cb->duration; - prev->duration = prev->timeend - prev->timestart; - } - } - else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { - DB_playItem_t *it = NULL; - if (cb->after) { - it = cb->after->next[PL_MAIN]; - } - else if (cb->last) { - it = playlist_head[PL_MAIN]; - } - if (it) { - for (; it != cb->last->next[PL_MAIN]; it = it->next[PL_MAIN]) { - char str[10]; - snprintf (str, 10, "%d", it->tracknum); - pl_add_meta (it, "track", str); - pl_add_meta (it, "title", NULL); - } - } - } -#endif -// }}} else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { const FLAC__StreamMetadata_VorbisComment *vc = &metadata->data.vorbis_comment; for (int i = 0; i < vc->num_comments; i++) { @@ -351,7 +310,7 @@ cflac_init_cue_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC_ s[c->length] = 0; memcpy (s, c->entry, c->length); if (!strncasecmp (s, "cuesheet=", 9)) { - cb->last = deadbeef->pl_insert_cue_from_buffer (cb->after, cb->fname, s+9, c->length-9, &plugin, "FLAC", cb->duration); + cb->last = deadbeef->pl_insert_cue_from_buffer (cb->after, cb->fname, s+9, c->length-9, &plugin, "FLAC", cb->totalsamples, cb->samplerate); } } } @@ -463,7 +422,7 @@ cflac_insert (DB_playItem_t *after, const char *fname) { } // try external cue - DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "flac", cb.duration); + DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "flac", cb.totalsamples, cb.samplerate); if (cue_after) { cue_after->timeend = cb.duration; cue_after->duration = cue_after->timeend - cue_after->timestart; @@ -527,6 +486,7 @@ static DB_decoder_t plugin = { .read_int16 = cflac_read_int16, .read_float32 = cflac_read_float32, .seek = cflac_seek, + .seek_sample = cflac_seek_sample, .insert = cflac_insert, .exts = exts, .id = "stdflac", diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c index 74847f37..c083b2c8 100644 --- a/plugins/mpgmad/mpgmad.c +++ b/plugins/mpgmad/mpgmad.c @@ -952,7 +952,8 @@ cmp3_insert (DB_playItem_t *after, const char *fname) { break; } } - DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, ftype, buffer.duration); + // FIXME! bad numsamples passed to cue + DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, ftype, buffer.duration*buffer.samplerate, buffer.samplerate); if (cue_after) { fclose (fp); return cue_after; diff --git a/plugins/vorbis/vorbis.c b/plugins/vorbis/vorbis.c index 6daba52a..d0eef1a9 100644 --- a/plugins/vorbis/vorbis.c +++ b/plugins/vorbis/vorbis.c @@ -153,7 +153,7 @@ cvorbis_insert (DB_playItem_t *after, const char *fname) { return NULL; } float duration = ov_time_total (&vorbis_file, -1); - DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "OggVorbis", duration); + DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "OggVorbis", duration, vi->rate); if (cue_after) { ov_clear (&vorbis_file); return cue_after; @@ -63,8 +63,8 @@ playItem_t str_streaming_song; static playItem_t *orig_playing_song; static playItem_t *orig_streaming_song; -#define trace(...) { fprintf(stderr, __VA_ARGS__); } -//#define trace(fmt,...) +//#define trace(...) { fprintf(stderr, __VA_ARGS__); } +#define trace(fmt,...) // playlist must call that whenever item was removed void |