summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-09-26 16:45:05 +0200
committerGravatar Alexey Yakovenko <wakeroid@gmail.com>2009-09-26 16:45:05 +0200
commita6c03469929bce31ce3c44cea1ecb795111edfc0 (patch)
treee9329739ba68fb65b7af05cf009bcf288b92cbcc
parentee549302a93a3c05cbfa49cfb6aceeea3aa6a951 (diff)
parent859a5adb43a05c84e5c6bec2e18f9946391f7c62 (diff)
Merge branch 'gapless'
-rw-r--r--README8
-rw-r--r--callbacks.c26
-rw-r--r--cdumb.c3
-rw-r--r--conf.c10
-rw-r--r--conf.h1
-rw-r--r--config-example18
-rw-r--r--configure.ac2
-rw-r--r--csid.cpp2
-rw-r--r--deadbeef.h20
-rw-r--r--gtkplaylist.c16
-rw-r--r--junklib.c257
-rw-r--r--main.c26
-rw-r--r--playlist.c155
-rw-r--r--playlist.h22
-rw-r--r--plugins.c14
-rw-r--r--plugins/ffap/ffap.c60
-rw-r--r--plugins/flac/flac.c194
-rw-r--r--plugins/lastfm/lastfm.c140
-rw-r--r--plugins/mpgmad/mpgmad.c665
-rw-r--r--plugins/vorbis/vorbis.c77
-rw-r--r--streamer.c270
-rw-r--r--streamer.h8
-rw-r--r--volume.c5
23 files changed, 1206 insertions, 793 deletions
diff --git a/README b/README
index e5d78056..03284345 100644
--- a/README
+++ b/README
@@ -1,7 +1,7 @@
-dependencies (player core, includes support for sid, game music and chiptune music):
- gtk+ 2.x (and gthread, and glib)
+dependencies for player core - includes support for sid, tracker music and chiptune music:
+ gtk+-2.0 >= 2.12 (+ gthread, + glib)
libsamplerate
- libalsa
+ alsa-lib
optional dependencies
libvorbis: for ogg vorbis plugin
@@ -9,5 +9,5 @@ optional dependencies
libmad: for mpgmad plugin (mpeg1,2 layers1,2,3)
libFLAC: for flac plugin
-if you want to build from git - install autotools, and run ./bootstrap.sh before compiling
+if you want to build from git - install autotools, and run ./autogen.sh before compiling
diff --git a/callbacks.c b/callbacks.c
index 26264582..2b300e19 100644
--- a/callbacks.c
+++ b/callbacks.c
@@ -642,8 +642,8 @@ on_voice1_clicked (GtkButton *button,
gpointer user_data)
{
codec_lock ();
- if (playlist_current.decoder && playlist_current.decoder->mutevoice) {
- playlist_current.decoder->mutevoice (0, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
+ if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) {
+ str_playing_song.decoder->mutevoice (0, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
}
codec_unlock ();
}
@@ -654,8 +654,8 @@ on_voice2_clicked (GtkButton *button,
gpointer user_data)
{
codec_lock ();
- if (playlist_current.decoder && playlist_current.decoder->mutevoice) {
- playlist_current.decoder->mutevoice (1, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
+ if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) {
+ str_playing_song.decoder->mutevoice (1, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
}
codec_unlock ();
}
@@ -666,8 +666,8 @@ on_voice3_clicked (GtkButton *button,
gpointer user_data)
{
codec_lock ();
- if (playlist_current.decoder && playlist_current.decoder->mutevoice) {
- playlist_current.decoder->mutevoice (2, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
+ if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) {
+ str_playing_song.decoder->mutevoice (2, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
}
codec_unlock ();
}
@@ -678,8 +678,8 @@ on_voice4_clicked (GtkButton *button,
gpointer user_data)
{
codec_lock ();
- if (playlist_current.decoder && playlist_current.decoder->mutevoice) {
- playlist_current.decoder->mutevoice (3, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
+ if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) {
+ str_playing_song.decoder->mutevoice (3, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
}
codec_unlock ();
}
@@ -690,8 +690,8 @@ on_voice5_clicked (GtkButton *button,
gpointer user_data)
{
codec_lock ();
- if (playlist_current.decoder && playlist_current.decoder->mutevoice) {
- playlist_current.decoder->mutevoice (4, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
+ if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) {
+ str_playing_song.decoder->mutevoice (4, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1);
}
codec_unlock ();
}
@@ -936,8 +936,8 @@ seekbar_draw (GtkWidget *widget) {
pos = x;
}
else {
- if (playlist_current.decoder && playlist_current.duration > 0) {
- pos = streamer_get_playpos () / playlist_current.duration;
+ if (str_playing_song.decoder && str_playing_song.duration > 0) {
+ pos = streamer_get_playpos () / str_playing_song.duration;
pos *= widget->allocation.width;
}
}
@@ -1024,7 +1024,7 @@ on_seekbar_button_release_event (GtkWidget *widget,
seekbar_moving = 0;
seekbar_draw (widget);
seekbar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height);
- float time = event->x * playlist_current.duration / (widget->allocation.width);
+ float time = event->x * str_playing_song.duration / (widget->allocation.width);
if (time < 0) {
time = 0;
}
diff --git a/cdumb.c b/cdumb.c
index a14a6ad7..2dae5754 100644
--- a/cdumb.c
+++ b/cdumb.c
@@ -739,9 +739,6 @@ cdumb_insert (DB_playItem_t *after, const char *fname) {
DB_playItem_t *it = deadbeef->pl_item_alloc ();
it->decoder = &plugin;
it->fname = strdup (fname);
- it->tracknum = 0;
- it->timestart = 0;
- it->timeend = 0;
DUMB_IT_SIGDATA * itsd = duh_get_it_sigdata(duh);
if (itsd->name[0]) {
deadbeef->pl_add_meta (it, "title", convstr ((char*)&itsd->name, sizeof(itsd->name)));
diff --git a/conf.c b/conf.c
index ff841c98..45d62dee 100644
--- a/conf.c
+++ b/conf.c
@@ -27,6 +27,7 @@ char conf_hvsc_path[1024] = "";
int conf_hvsc_enable = 0;
char conf_blacklist_plugins[1024]; // plugins listed in this option will not be loaded
int conf_close_send_to_tray = 0;
+int conf_replaygain_mode = 0;
int
conf_load (void) {
@@ -93,6 +94,15 @@ conf_load (void) {
else if (!strcasecmp (str, "close_send_to_tray")) {
conf_close_send_to_tray = atoi (value);
}
+ else if (!strcasecmp (str, "replaygain_mode")) {
+ int rg = atoi (value);
+ if (rg >= 0 && rg <= 2) {
+ conf_replaygain_mode = atoi (value);
+ }
+ else {
+ fprintf (stderr, "config warning: replaygain_mode must be one of 0, 1 or 2\n");
+ }
+ }
else {
fprintf (stderr, "error in config file line %d\n", line);
}
diff --git a/conf.h b/conf.h
index 39f41e3f..dc04f8ce 100644
--- a/conf.h
+++ b/conf.h
@@ -25,6 +25,7 @@ extern char conf_hvsc_path[1024];
extern int conf_hvsc_enable;
extern char conf_blacklist_plugins[1024];
extern int conf_close_send_to_tray;
+extern int conf_replaygain_mode;
int
conf_load (void);
diff --git a/config-example b/config-example
index 7a5bf47e..104fe1f6 100644
--- a/config-example
+++ b/config-example
@@ -7,9 +7,12 @@
alsa_soundcard default
# mixing frequency (samplerate)
-# prefer native samplerate of your soundcard
-# e.g. 48k for creative cards
+# you should prefer native samplerate of your soundcard
+# that's 48k for creative cards
# consult your soundcard manual if you don't want to get crappy results
+# this parameter heavily impacts quality and CPU use
+# if you set this to wrong value - you will loose both
+# default is 48000
samplerate 48000
# samplerate conversion quality
@@ -26,7 +29,7 @@ samplerate 48000
src_quality 2
# default behaviour of close(X) button is to close player
-# this option allows to minimize to tray instead
+# set to 1 to minimize to tray instead
#close_send_to_tray 1
# those next 2 are disabled by default, cause most people probably don't have HVSC
@@ -36,4 +39,13 @@ src_quality 2
# plugin blacklisting
# i use it for testing
+# but it can also be used to save some memory by ignoring unused plugins
#blacklist_plugins ape hotkeys
+
+# replay gain mode
+# see http://wiki.hydrogenaudio.org/index.php?title=Replaygain for details
+# 0 none (default)
+# 1 track
+# 2 album (audiophile)
+# you must preprocess your collection with some external application to use that
+replaygain_mode 0
diff --git a/configure.ac b/configure.ac
index 671cde41..8146b68a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,7 +3,7 @@ AC_INIT
AC_CONFIG_HEADER(config.h)
PACKAGE="deadbeef"
-VERSION="0.2.2.2"
+VERSION="0.2.3-dev"
AM_INIT_AUTOMAKE($PACKAGE,$VERSION)
diff --git a/csid.cpp b/csid.cpp
index 7ec92946..5a8ffbf2 100644
--- a/csid.cpp
+++ b/csid.cpp
@@ -495,8 +495,6 @@ csid_insert (DB_playItem_t *after, const char *fname) {
it->decoder = &plugin;
it->fname = strdup (fname);
it->tracknum = s;
- it->timestart = 0;
- it->timeend = 0;
SidTuneInfo sidinfo;
tune->getInfo (sidinfo);
int i = sidinfo.numberOfInfoStrings;
diff --git a/deadbeef.h b/deadbeef.h
index fc9fd897..ddff5aed 100644
--- a/deadbeef.h
+++ b/deadbeef.h
@@ -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,20 +64,22 @@ 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)
- float timestart; // start time of cue track, or -1
- float timeend; // end time of cue track, or -1
+ int startsample; // start sample of track, or -1 for auto
+ int endsample; // end sample of track, or -1 for auto
float duration; // in seconds
- int startoffset; // offset to seek to skip tags and info-headers
- int endoffset; // offset from end of file where music data ends
int shufflerating; // sort order for shuffle mode
float playtime; // total playtime
time_t started_timestamp; // result of calling time(NULL)
const char *filetype; // e.g. MP3 or OGG
+ float replaygain_album_gain;
+ float replaygain_album_peak;
+ float replaygain_track_gain;
+ float replaygain_track_peak;
} DB_playItem_t;
// plugin types
@@ -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
diff --git a/gtkplaylist.c b/gtkplaylist.c
index 0c39cde9..6a434b83 100644
--- a/gtkplaylist.c
+++ b/gtkplaylist.c
@@ -44,6 +44,9 @@
#include "drawing.h"
#include "session.h"
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
// orange on dark color scheme
float colo_dark_orange[COLO_COUNT][3] = {
{ 0x7f/255.f, 0x7f/255.f, 0x7f/255.f }, // cursor
@@ -99,7 +102,7 @@ gtkpl_init (void) {
//memcpy (colo_current, colo_dark_orange, sizeof (colo_current));
play16_pixbuf = draw_load_pixbuf ("play_16.png");
pause16_pixbuf = draw_load_pixbuf ("pause_16.png");
- rowheight = draw_get_font_size () + 10;
+ rowheight = draw_get_font_size () + 12;
memcpy (colo_current, colo_white_blue, sizeof (colo_current));
}
@@ -237,7 +240,7 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) {
}
int width, height;
draw_get_canvas_size ((uintptr_t)ps->backbuf, &width, &height);
- if (it == playlist_current_ptr && ps->colwidths[0] > 0/* && !p_isstopped ()*/) {
+ if (it == playlist_current_ptr && ps->colwidths[0] > 0 && !p_isstopped ()) {
uintptr_t pixbuf = p_ispaused () ? pause16_pixbuf : play16_pixbuf;
draw_pixbuf ((uintptr_t)ps->backbuf, pixbuf, ps->colwidths[0]/2-8-ps->hscrollpos, (row - ps->scrollpos) * rowheight + rowheight/2 - 8, 0, 0, 16, 16);
}
@@ -301,10 +304,10 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) {
int dotpos;
int cidx = ((row-ps->scrollpos) * pl_ncolumns + i) * 3;
if (i == 2) {
- draw_text_with_colors (x+5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2, ps->colwidths[i]-10, 1, columns[i]);
+ draw_text_with_colors (x+5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, ps->colwidths[i]-10, 1, columns[i]);
}
else {
- draw_text_with_colors (x + 5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2, ps->colwidths[i]-10, 0, columns[i]);
+ draw_text_with_colors (x + 5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, ps->colwidths[i]-10, 0, columns[i]);
}
}
x += ps->colwidths[i];
@@ -775,6 +778,11 @@ gtkpl_keypress (gtkplaylist_t *ps, int keyval, int state) {
newscroll = ps->row - widget->allocation.height / rowheight + 1;
}
}
+ else if (keyval == GDK_r) {
+ extern int replaygain;
+ replaygain = 1-replaygain;
+ fprintf (stderr, "replaygain=%d\n", replaygain);
+ }
else if (keyval == GDK_Up && ps->row > 0) {
ps->row--;
if (ps->row < ps->scrollpos) {
diff --git a/junklib.c b/junklib.c
index 43bf6668..0169e1a8 100644
--- a/junklib.c
+++ b/junklib.c
@@ -23,11 +23,13 @@
#include "playlist.h"
#include "utf8.h"
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
static uint32_t
-extract_i32 (unsigned char *buf)
+extract_i32 (const uint8_t *buf)
{
uint32_t x;
// big endian extract
@@ -60,7 +62,7 @@ extract_i32_le (unsigned char *buf)
return x;
}
static inline uint16_t
-extract_i16 (unsigned char *buf)
+extract_i16 (const uint8_t *buf)
{
uint16_t x;
// big endian extract
@@ -263,8 +265,7 @@ convstr_id3v2_2to3 (const unsigned char* str, int sz) {
char *ret = out;
// hack to add limited cp1251 recoding support
-
- if (*str == 1) {
+ if (*str == 1) {
if (str[1] == 0xff && str[2] == 0xfe) {
enc = "UCS-2LE";
}
@@ -272,7 +273,7 @@ convstr_id3v2_2to3 (const unsigned char* str, int sz) {
enc = "UCS-2BE";
}
else {
- fprintf (stderr, "invalid ucs-2 signature %x %x\n", (int)str[1], (int)str[2]);
+ trace ("invalid ucs-2 signature %x %x\n", (int)str[1], (int)str[2]);
return NULL;
}
}
@@ -285,7 +286,7 @@ convstr_id3v2_2to3 (const unsigned char* str, int sz) {
sz--;
iconv_t cd = iconv_open ("utf8", enc);
if (!cd) {
-// fprintf (stderr, "unknown encoding: %s\n", enc);
+ trace ("unknown encoding: %s\n", enc);
return NULL;
}
else {
@@ -311,19 +312,23 @@ convstr_id3v2_4 (const unsigned char* str, int sz) {
if (*str == 0) {
// iso8859-1
+ trace ("iso8859-1\n");
enc = "iso8859-1";
}
else if (*str == 3) {
// utf8
+ trace ("utf8\n");
strncpy (out, str+1, 2047);
sz--;
out[min (sz, 2047)] = 0;
return strdup (out);
}
else if (*str == 1) {
+ trace ("utf16\n");
enc = "UTF-16";
}
else if (*str == 2) {
+ trace ("utf16be\n");
enc = "UTF-16BE";
}
else {
@@ -335,7 +340,7 @@ convstr_id3v2_4 (const unsigned char* str, int sz) {
sz--;
iconv_t cd = iconv_open ("utf8", enc);
if (!cd) {
- // printf ("unknown encoding: %s\n", enc);
+ trace ("unknown encoding: %s\n", enc);
return NULL;
}
else {
@@ -348,7 +353,7 @@ convstr_id3v2_4 (const unsigned char* str, int sz) {
iconv_close (cd);
ret = out;
}
- //fprintf (stderr, "decoded %s\n", out+3);
+// trace ("decoded %s\n", out+3);
return strdup (ret);
}
@@ -388,7 +393,7 @@ convstr_id3v1 (const char* str, int sz) {
}
cd = iconv_open ("utf8", enc);
if (!cd) {
- // fprintf (stderr, "unknown encoding: %s\n", enc);
+ // trace ("unknown encoding: %s\n", enc);
return NULL;
}
else {
@@ -417,7 +422,7 @@ str_trim_right (uint8_t *str, int len) {
int
junk_read_id3v1 (playItem_t *it, FILE *fp) {
if (!it || !fp) {
- fprintf (stderr, "bad call to junk_read_id3v1!\n");
+ trace ("bad call to junk_read_id3v1!\n");
return -1;
}
uint8_t buffer[128];
@@ -469,7 +474,7 @@ junk_read_id3v1 (playItem_t *it, FILE *fp) {
}
// add meta
-// fprintf (stderr, "%s - %s - %s - %s - %s - %s\n", title, artist, album, year, comment, genre);
+// trace ("%s - %s - %s - %s - %s - %s\n", title, artist, album, year, comment, genre);
pl_add_meta (it, "title", convstr_id3v1 (title, strlen (title)));
pl_add_meta (it, "artist", convstr_id3v1 (artist, strlen (artist)));
pl_add_meta (it, "album", convstr_id3v1 (album, strlen (album)));
@@ -482,20 +487,21 @@ junk_read_id3v1 (playItem_t *it, FILE *fp) {
pl_add_meta (it, "track", s);
}
- if (it->endoffset < 128) {
- it->endoffset = 128;
- }
+// FIXME: that should be accounted for
+// if (it->endoffset < 128) {
+// it->endoffset = 128;
+// }
return 0;
}
int
junk_read_ape (playItem_t *it, FILE *fp) {
-// fprintf (stderr, "trying to read ape tag\n");
+// trace ("trying to read ape tag\n");
// try to read footer, position must be already at the EOF right before
// id3v1 (if present)
uint8_t header[32];
- if (fseek (fp, -32, SEEK_CUR) == -1) {
+ if (fseek (fp, -32, SEEK_END) == -1) {
return -1; // something bad happened
}
@@ -503,12 +509,21 @@ junk_read_ape (playItem_t *it, FILE *fp) {
return -1; // something bad happened
}
if (strncmp (header, "APETAGEX", 8)) {
- return -1; // no ape tag here
+ // try to skip 128 bytes backwards (id3v1)
+ if (fseek (fp, -128-32, SEEK_END) == -1) {
+ return -1; // something bad happened
+ }
+ if (fread (header, 1, 32, fp) != 32) {
+ return -1; // something bad happened
+ }
+ if (strncmp (header, "APETAGEX", 8)) {
+ return -1; // no ape tag here
+ }
}
// end of footer must be 0
// if (memcmp (&header[24], "\0\0\0\0\0\0\0\0", 8)) {
-// fprintf (stderr, "bad footer\n");
+// trace ("bad footer\n");
// return -1;
// }
@@ -517,10 +532,10 @@ junk_read_ape (playItem_t *it, FILE *fp) {
uint32_t numitems = extract_i32_le (&header[16]);
uint32_t flags = extract_i32_le (&header[20]);
- //fprintf (stderr, "APEv%d, size=%d, items=%d, flags=%x\n", version, size, numitems, flags);
+ //trace ("APEv%d, size=%d, items=%d, flags=%x\n", version, size, numitems, flags);
// now seek to beginning of the tag (exluding header)
if (fseek (fp, -size, SEEK_CUR) == -1) {
- fprintf (stderr, "failed to seek to tag start (-%d)\n", size);
+ trace ("failed to seek to tag start (-%d)\n", size);
return -1;
}
int i;
@@ -580,6 +595,22 @@ junk_read_ape (playItem_t *it, FILE *fp) {
else if (!strcasecmp (key, "comment")) {
pl_add_meta (it, "genre", value);
}
+ else if (!strncasecmp (key, "replaygain_album_gain", 21)) {
+ it->replaygain_album_gain = atof (value);
+ trace ("album_gain=%s\n", value);
+ }
+ else if (!strncasecmp (key, "replaygain_album_peak", 21)) {
+ it->replaygain_album_peak = atof (value);
+ trace ("album_peak=%s\n", value);
+ }
+ else if (!strncasecmp (key, "replaygain_track_gain", 21)) {
+ it->replaygain_track_gain = atof (value);
+ trace ("track_gain=%s\n", value);
+ }
+ else if (!strncasecmp (key, "replaygain_track_peak", 21)) {
+ it->replaygain_track_peak = atof (value);
+ trace ("track_peak=%s\n", value);
+ }
}
}
@@ -587,27 +618,29 @@ junk_read_ape (playItem_t *it, FILE *fp) {
}
static void
-id3v2_string_read (int version, uint8_t *out, int sz, int unsync, uint8_t **pread) {
+id3v2_string_read (int version, uint8_t *out, int sz, int unsync, const uint8_t *pread) {
if (!unsync) {
- memcpy (out, *pread, sz);
- *pread += sz;
+ memcpy (out, pread, sz);
out[sz] = 0;
out[sz+1] = 0;
return;
}
uint8_t prev = 0;
while (sz > 0) {
- if (prev == 0xff && !*(*pread)) {
+ if (prev == 0xff && !(*pread)) {
+// trace ("found unsync 0x00 byte\n");
prev = 0;
- (*pread)++;
+ pread++;
continue;
}
- prev = *out = *(*pread);
- (*pread)++;
+ prev = *out = *pread;
+// trace ("%02x ", prev);
+ pread++;
out++;
sz--;
}
- *out = 0;
+// trace ("\n");
+ *out++ = 0;
}
int
@@ -640,7 +673,7 @@ junk_get_leading_size (FILE *fp) {
return -1; // bad header
}
uint32_t size = (header[9] << 0) | (header[8] << 7) | (header[7] << 14) | (header[6] << 21);
- //fprintf (stderr, "junklib: leading junk size %d\n", size);
+ //trace ("junklib: leading junk size %d\n", size);
return size + 10 + 10 * footerpresent;
}
@@ -648,7 +681,7 @@ int
junk_read_id3v2 (playItem_t *it, FILE *fp) {
int title_added = 0;
if (!it || !fp) {
- fprintf (stderr, "bad call to junk_read_id3v2!\n");
+ trace ("bad call to junk_read_id3v2!\n");
return -1;
}
rewind (fp);
@@ -662,7 +695,7 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
uint8_t version_major = header[3];
uint8_t version_minor = header[4];
if (version_major > 4 || version_major < 2) {
-// fprintf (stderr, "id3v2.%d.%d is unsupported\n", version_major, version_minor);
+// trace ("id3v2.%d.%d is unsupported\n", version_major, version_minor);
return -1; // unsupported
}
uint8_t flags = header[5];
@@ -678,13 +711,13 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
return -1; // bad header
}
uint32_t size = (header[9] << 0) | (header[8] << 7) | (header[7] << 14) | (header[6] << 21);
- int startoffset = size + 10 + 10 * footerpresent;
- if (startoffset > it->startoffset) {
- it->startoffset = startoffset;
-// fprintf (stderr, "id3v2 end: %x\n", startoffset);
- }
+ // FIXME: that should be accounted for
+// int startoffset = size + 10 + 10 * footerpresent;
+// if (startoffset > it->startoffset) {
+// it->startoffset = startoffset;
+// }
-// fprintf (stderr, "tag size: %d\n", size);
+// trace ("tag size: %d\n", size);
// try to read full tag if size is small enough
@@ -697,7 +730,7 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
}
uint8_t *readptr = tag;
int crcpresent = 0;
-// fprintf (stderr, "version: 2.%d.%d, unsync: %d, extheader: %d, experimental: %d\n", version_major, version_minor, unsync, extheader, expindicator);
+ trace ("version: 2.%d.%d, unsync: %d, extheader: %d, experimental: %d\n", version_major, version_minor, unsync, extheader, expindicator);
if (extheader) {
if (size < 6) {
@@ -741,13 +774,19 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
frameid[4] = 0;
readptr += 4;
if (readptr - tag >= size - 4) {
- err = 1;
break;
}
- uint32_t sz = (readptr[3] << 0) | (readptr[2] << 8) | (readptr[1] << 16) | (readptr[0] << 24);
+ uint32_t sz;
+ if (version_major == 4) {
+ sz = (readptr[3] << 0) | (readptr[2] << 7) | (readptr[1] << 14) | (readptr[0] << 21);
+ }
+ else if (version_major == 3) {
+ sz = (readptr[3] << 0) | (readptr[2] << 8) | (readptr[1] << 16) | (readptr[0] << 24);
+ }
readptr += 4;
- //fprintf (stderr, "got frame %s, size %d, pos %d, tagsize %d\n", frameid, sz, readptr-tag, size);
+ trace ("got frame %s, size %d, pos %d, tagsize %d\n", frameid, sz, readptr-tag, size);
if (readptr - tag >= size - sz) {
+ trace ("frame is out of tag bounds\n");
err = 1;
break; // size of frame is more than size of tag
}
@@ -755,16 +794,81 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
// err = 1;
break; // frame must be at least 1 byte long
}
- uint16_t flags = (readptr[1] << 0) | (readptr[0] << 8);
+ uint8_t flags1 = readptr[0];
+ uint8_t flags2 = readptr[1];
readptr += 2;
-// fprintf (stderr, "found id3v2.3 frame: %s, size=%d\n", frameid, sz);
+ if (version_major == 4) {
+ if (flags1 & 0x8f) {
+ // unknown flags
+ trace ("unknown status flags: %02x\n", flags1);
+ readptr += sz;
+ continue;
+ }
+ if (flags2 & 0xb0) {
+ // unknown flags
+ trace ("unknown format flags: %02x\n", flags2);
+ readptr += sz;
+ continue;
+ }
+
+ if (flags2 & 0x40) { // group id
+ trace ("frame has group id\n");
+ readptr++; // skip id
+ sz--;
+ }
+ if (flags2 & 0x08) { // compressed frame, ignore
+ trace ("frame is compressed, skipping\n");
+ readptr += sz;
+ continue;
+ }
+ if (flags2 & 0x04) { // encrypted frame, skip
+ trace ("frame is encrypted, skipping\n");
+ readptr += sz;
+ continue;
+ }
+ if (flags2 & 0x02) { // unsync, just do nothing
+ }
+ if (flags2 & 0x01) { // data size
+ uint32_t size = extract_i32 (readptr);
+ trace ("frame has extra size field = %d\n", size);
+ readptr += 4;
+ sz -= 4;
+ }
+ }
+ else if (version_major == 3) {
+ if (flags1 & 0x1F) {
+ trace ("unknown status flags: %02x\n", flags1);
+ readptr += sz;
+ continue;
+ }
+ if (flags2 & 0x1F) {
+ trace ("unknown format flags: %02x\n", flags2);
+ readptr += sz;
+ continue;
+ }
+ if (flags2 & 0x80) {
+ trace ("frame is compressed, skipping\n");
+ readptr += sz;
+ continue;
+ }
+ if (flags2 & 0x40) {
+ trace ("frame is encrypted, skipping\n");
+ readptr += sz;
+ continue;
+ }
+ if (flags2 & 0x20) {
+ trace ("frame has group id\n");
+ readptr++; // skip id
+ sz--;
+ }
+ }
if (!strcmp (frameid, "TPE1")) {
if (sz > 1000) {
err = 1;
break; // too large
}
char str[sz+2];
- id3v2_string_read (version_major, &str[0], sz, unsync, &readptr);
+ id3v2_string_read (version_major, &str[0], sz, unsync, readptr);
artist = convstr (str, sz);
}
else if (!strcmp (frameid, "TPE2")) {
@@ -773,7 +877,7 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
break; // too large
}
char str[sz+2];
- id3v2_string_read (version_major, &str[0], sz, unsync, &readptr);
+ id3v2_string_read (version_major, &str[0], sz, unsync, readptr);
band = convstr (str, sz);
}
else if (!strcmp (frameid, "TRCK")) {
@@ -782,16 +886,18 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
break; // too large
}
char str[sz+2];
- id3v2_string_read (version_major, &str[0], sz, unsync, &readptr);
+ id3v2_string_read (version_major, &str[0], sz, unsync, readptr);
track = convstr (str, sz);
}
else if (!strcmp (frameid, "TIT2")) {
+ trace ("parsing TIT2...\n");
if (sz > 1000) {
err = 1;
break; // too large
}
+ trace ("parsing TIT2....\n");
char str[sz+2];
- id3v2_string_read (version_major, &str[0], sz, unsync, &readptr);
+ id3v2_string_read (version_major, &str[0], sz, unsync, readptr);
title = convstr (str, sz);
}
else if (!strcmp (frameid, "TALB")) {
@@ -800,12 +906,56 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
break; // too large
}
char str[sz+2];
- id3v2_string_read (version_major, &str[0], sz, unsync, &readptr);
+ id3v2_string_read (version_major, &str[0], sz, unsync, readptr);
album = convstr (str, sz);
}
- else {
- readptr += sz;
+ else if (!strcmp (frameid, "COMM")) {
}
+ else if (!strcmp (frameid, "TXXX")) {
+ if (sz < 2) {
+ trace ("TXXX frame is too short, skipped\n");
+ readptr += sz; // bad tag
+ continue;
+ }
+ uint8_t *p = readptr;
+ uint8_t encoding = *p;
+ p++;
+ uint8_t *desc = p;
+ int desc_sz = 0;
+ while (*p && p - readptr < sz) {
+ p++;
+ desc_sz++;
+ }
+ p++;
+ if (p - readptr >= sz) {
+ trace ("bad TXXX frame, skipped\n");
+ readptr += sz; // bad tag
+ continue;
+ }
+ char desc_s[desc_sz+2];
+ id3v2_string_read (version_major, desc_s, desc_sz, unsync, desc);
+ //trace ("desc=%s\n", desc_s);
+ char value_s[readptr+sz-p+2];
+ id3v2_string_read (version_major, value_s, readptr+sz-p, unsync, p);
+ //trace ("value=%s\n", value_s);
+ if (!strcasecmp (desc_s, "replaygain_album_gain")) {
+ it->replaygain_album_gain = atof (value_s);
+ trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_album_gain);
+ }
+ else if (!strcasecmp (desc_s, "replaygain_album_peak")) {
+ it->replaygain_album_peak = atof (value_s);
+ trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_album_peak);
+ }
+ else if (!strcasecmp (desc_s, "replaygain_track_gain")) {
+ it->replaygain_track_gain = atof (value_s);
+ trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_track_gain);
+ }
+ else if (!strcasecmp (desc_s, "replaygain_track_peak")) {
+ it->replaygain_track_peak = atof (value_s);
+ trace ("%s=%s (%f)\n", desc_s, value_s, it->replaygain_track_peak);
+ }
+ }
+ readptr += sz;
}
else if (version_major == 2) {
char frameid[4];
@@ -823,7 +973,7 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
if (sz < 1) {
break; // frame must be at least 1 byte long
}
-// fprintf (stderr, "found id3v2.2 frame: %s, size=%d\n", frameid, sz);
+// trace ("found id3v2.2 frame: %s, size=%d\n", frameid, sz);
if (!strcmp (frameid, "TEN")) {
char str[sz+2];
memcpy (str, readptr, sz);
@@ -878,7 +1028,7 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
readptr += sz;
}
else {
-// fprintf (stderr, "id3v2.%d (unsupported!)\n", version_minor);
+// trace ("id3v2.%d (unsupported!)\n", version_minor);
}
}
if (!err) {
@@ -911,6 +1061,9 @@ junk_read_id3v2 (playItem_t *it, FILE *fp) {
}
return 0;
}
+ else {
+ trace ("error parsing id3v2\n");
+ }
return -1;
}
diff --git a/main.c b/main.c
index bde856ee..eb1deec8 100644
--- a/main.c
+++ b/main.c
@@ -95,21 +95,21 @@ update_songinfo (void) {
else if (p_isstopped ()) {
strcpy (sbtext_new, "Stopped");
}
- else if (playlist_current.decoder) {
+ else if (str_playing_song.decoder) {
codec_lock ();
- DB_decoder_t *c = playlist_current.decoder;
+ DB_decoder_t *c = str_playing_song.decoder;
float playpos = streamer_get_playpos ();
int minpos = playpos / 60;
int secpos = playpos - minpos * 60;
- int mindur = playlist_current.duration / 60;
- int secdur = playlist_current.duration - mindur * 60;
+ int mindur = str_playing_song.duration / 60;
+ int secdur = str_playing_song.duration - mindur * 60;
const char *mode = c->info.channels == 1 ? "Mono" : "Stereo";
int samplerate = c->info.samplerate;
int bitspersample = c->info.bps;
songpos = playpos;
codec_unlock ();
- snprintf (sbtext_new, 512, "[%s] %dHz | %d bit | %s | %d:%02d / %d:%02d | %d songs total", playlist_current.filetype ? playlist_current.filetype:"-", samplerate, bitspersample, mode, minpos, secpos, mindur, secdur, pl_getcount ());
+ snprintf (sbtext_new, 512, "[%s] %dHz | %d bit | %s | %d:%02d / %d:%02d | %d songs total", str_playing_song.filetype ? str_playing_song.filetype:"-", samplerate, bitspersample, mode, minpos, secpos, mindur, secdur, pl_getcount ());
}
if (strcmp (sbtext_new, sb_text)) {
@@ -135,7 +135,7 @@ update_songinfo (void) {
if (mainwin) {
GtkWidget *widget = lookup_widget (mainwin, "seekbar");
// translate volume to seekbar pixels
- songpos /= playlist_current.duration;
+ songpos /= str_playing_song.duration;
songpos *= widget->allocation.width;
if ((int)(songpos*2) != (int)(last_songpos*2)) {
GDK_THREADS_ENTER();
@@ -357,12 +357,14 @@ player_thread (uintptr_t ctx) {
if (from >= 0 || to >= 0) {
if (to >= 0) {
playItem_t *it = pl_get_for_idx (to);
- char str[600];
- char dname[512];
- pl_format_item_display_name (it, dname, 512);
- snprintf (str, 600, "DeaDBeeF - %s", dname);
- gtk_window_set_title (GTK_WINDOW (mainwin), str);
- set_tray_tooltip (str);
+ if (it) { // it might have been deleted after event was sent
+ char str[600];
+ char dname[512];
+ pl_format_item_display_name (it, dname, 512);
+ snprintf (str, 600, "DeaDBeeF - %s", dname);
+ gtk_window_set_title (GTK_WINDOW (mainwin), str);
+ set_tray_tooltip (str);
+ }
}
else {
gtk_window_set_title (GTK_WINDOW (mainwin), "DeaDBeeF");
diff --git a/playlist.c b/playlist.c
index d91cb2a1..feb7f789 100644
--- a/playlist.c
+++ b/playlist.c
@@ -35,18 +35,18 @@
#include "plugins.h"
#include "junklib.h"
-//#include "cvorbis.h"
-//#include "cdumb.h"
-//#include "cmp3.h"
-//#include "cgme.h"
-//#include "cflac.h"
-//#include "csid.h"
+// 1.0->1.1 changelog:
+// added sample-accurate seek positions for sub-tracks
+#define PLAYLIST_MAJOR_VER 1
+#define PLAYLIST_MINOR_VER 1
+
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
#define SKIP_BLANK_CUE_TRACKS 1
playItem_t *playlist_head[PL_MAX_ITERATORS];
playItem_t *playlist_tail[PL_MAX_ITERATORS];
-playItem_t playlist_current;
playItem_t *playlist_current_ptr;
int pl_count = 0;
static int pl_order = 0; // 0 = linear, 1 = shuffle, 2 = random
@@ -147,7 +147,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;
}
@@ -170,19 +170,25 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c
float f_index01 = index01[0] ? pl_cue_parse_time (index01) : 0;
float f_pregap = pregap[0] ? pl_cue_parse_time (pregap) : 0;
if (*prev) {
+ float prevtime = 0;
if (pregap[0] && index01[0]) {
// PREGAP command
- (*prev)->timeend = f_index01 - f_pregap;
+ prevtime = f_index01 - f_pregap;
}
else if (index00[0]) {
// pregap in index 00
- (*prev)->timeend = f_index00;
+ prevtime = f_index00;
}
else if (index01[0]) {
// no pregap
- (*prev)->timeend = f_index01;
+ prevtime = f_index01;
+ }
+ else {
+ return after;
}
- (*prev)->duration = (*prev)->timeend - (*prev)->timestart;
+ (*prev)->endsample = (prevtime * samplerate) - 1;
+ trace ("calc endsample=%d, prevtime=%f, samplerate=%d\n", (*prev)->endsample, prevtime, samplerate);
+ (*prev)->duration = (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate;
}
// non-compliant hack to handle tracks which only store pregap info
if (!index01[0]) {
@@ -194,14 +200,16 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c
it->decoder = decoder;
it->fname = strdup (fname);
it->tracknum = atoi (track);
+ float t = 0;
if (index01[0]) {
- it->timestart = f_index01;
+ t = f_index01;
}
else {
- it->timestart = f_index00;
+ t = f_index00;
}
+ it->startsample = t * samplerate;
- it->timeend = -1; // will be filled by next read, or by decoder
+ it->endsample = -1; // will be filled by next read, or by decoder
it->filetype = ftype;
after = pl_insert_item (after, it);
pl_add_meta (it, "artist", performer);
@@ -213,7 +221,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] = "";
@@ -259,7 +268,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;
@@ -285,16 +294,17 @@ 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->duration = after->timeend - after->timestart;
+ after->endsample = numsamples-1;
+ after->duration = (float)(after->endsample - after->startsample + 1) / samplerate;
}
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);
@@ -324,7 +334,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 *
@@ -474,6 +484,7 @@ int
pl_remove (playItem_t *it) {
if (!it)
return -1;
+ streamer_song_removed_notify (it);
pl_count--;
if (playlist_current_ptr == it) {
playlist_current_ptr = NULL;
@@ -590,13 +601,15 @@ pl_item_copy (playItem_t *out, playItem_t *it) {
out->fname = strdup (it->fname);
out->decoder = it->decoder;
out->tracknum = it->tracknum;
- out->timestart = it->timestart;
- out->timeend = it->timeend;
+ out->startsample = it->startsample;
+ out->endsample = it->endsample;
out->duration = it->duration;
- out->startoffset = it->startoffset;
- out->endoffset = it->endoffset;
out->shufflerating = it->shufflerating;
out->filetype = it->filetype;
+ out->replaygain_album_gain = it->replaygain_album_gain;
+ out->replaygain_album_peak = it->replaygain_album_peak;
+ out->replaygain_track_gain = it->replaygain_track_gain;
+ out->replaygain_track_peak = it->replaygain_track_peak;
out->started_timestamp = it->started_timestamp;
out->next[PL_MAIN] = it->next[PL_MAIN];
out->prev[PL_MAIN] = it->prev[PL_MAIN];
@@ -645,46 +658,6 @@ pl_item_free (playItem_t *it) {
}
int
-pl_set_current (playItem_t *it) {
- int ret = 0;
- int from = pl_get_idx_of (playlist_current_ptr);
- int to = it ? pl_get_idx_of (it) : -1;
- if (playlist_current.decoder) {
- plug_trigger_event (DB_EV_SONGFINISHED);
- }
- codec_lock ();
- if (playlist_current.decoder) {
- playlist_current.decoder->free ();
- }
- pl_item_free (&playlist_current);
- playlist_current_ptr = it;
- if (it && it->decoder) {
- // don't do anything on fail, streamer will take care
- ret = it->decoder->init (DB_PLAYITEM (it));
- if (ret < 0) {
-// pl_item_free (&playlist_current);
-// playlist_current_ptr = NULL;
-// return ret;
-//// it->decoder->info.samplesPerSecond = -1;
- }
- }
- if (playlist_current_ptr) {
- streamer_reset (0);
- }
- if (it) {
- it->played = 1;
- it->started_timestamp = time (NULL);
- pl_item_copy (&playlist_current, it);
- }
- codec_unlock ();
- if (it) {
- plug_trigger_event (DB_EV_SONGSTARTED);
- }
- messagepump_push (M_SONGCHANGED, 0, from, to);
- return ret;
-}
-
-int
pl_prevsong (void) {
if (!playlist_head[PL_MAIN]) {
streamer_set_nextsong (-2, 1);
@@ -1027,8 +1000,8 @@ pl_set_loop_mode (int mode) {
int
pl_save (const char *fname) {
const char magic[] = "DBPL";
- uint8_t majorver = 1;
- uint8_t minorver = 0;
+ uint8_t majorver = PLAYLIST_MAJOR_VER;
+ uint8_t minorver = PLAYLIST_MINOR_VER;
FILE *fp = fopen (fname, "w+b");
if (!fp) {
return -1;
@@ -1067,10 +1040,10 @@ pl_save (const char *fname) {
if (fwrite (&l, 1, 2, fp) != 2) {
goto save_fail;
}
- if (fwrite (&it->timestart, 1, 4, fp) != 4) {
+ if (fwrite (&it->startsample, 1, 4, fp) != 4) {
goto save_fail;
}
- if (fwrite (&it->timeend, 1, 4, fp) != 4) {
+ if (fwrite (&it->endsample, 1, 4, fp) != 4) {
goto save_fail;
}
if (fwrite (&it->duration, 1, 4, fp) != 4) {
@@ -1085,6 +1058,19 @@ pl_save (const char *fname) {
goto save_fail;
}
}
+ if (fwrite (&it->replaygain_album_gain, 1, 4, fp) != 4) {
+ goto save_fail;
+ }
+ if (fwrite (&it->replaygain_album_peak, 1, 4, fp) != 4) {
+ goto save_fail;
+ }
+ if (fwrite (&it->replaygain_track_gain, 1, 4, fp) != 4) {
+ goto save_fail;
+ }
+ if (fwrite (&it->replaygain_track_peak, 1, 4, fp) != 4) {
+ goto save_fail;
+ }
+
int16_t nm = 0;
metaInfo_t *m;
for (m = it->meta; m; m = m->next) {
@@ -1126,8 +1112,8 @@ int
pl_load (const char *fname) {
pl_free ();
DB_decoder_t **decoders = plug_get_decoder_list ();
- uint8_t majorver = 1;
- uint8_t minorver = 0;
+ uint8_t majorver;
+ uint8_t minorver;
FILE *fp = fopen (fname, "rb");
if (!fp) {
return -1;
@@ -1142,13 +1128,13 @@ pl_load (const char *fname) {
if (fread (&majorver, 1, 1, fp) != 1) {
goto load_fail;
}
- if (majorver != 1) {
+ if (majorver != PLAYLIST_MAJOR_VER) {
goto load_fail;
}
if (fread (&minorver, 1, 1, fp) != 1) {
goto load_fail;
}
- if (minorver != 0) {
+ if (minorver != PLAYLIST_MINOR_VER) {
goto load_fail;
}
uint32_t cnt;
@@ -1198,12 +1184,12 @@ pl_load (const char *fname) {
goto load_fail;
}
it->tracknum = l;
- // timestart
- if (fread (&it->timestart, 1, 4, fp) != 4) {
+ // startsample
+ if (fread (&it->startsample, 1, 4, fp) != 4) {
goto load_fail;
}
- // timeend
- if (fread (&it->timeend, 1, 4, fp) != 4) {
+ // endsample
+ if (fread (&it->endsample, 1, 4, fp) != 4) {
goto load_fail;
}
// duration
@@ -1230,6 +1216,18 @@ pl_load (const char *fname) {
}
}
}
+ if (fread (&it->replaygain_album_gain, 1, 4, fp) != 4) {
+ goto load_fail;
+ }
+ if (fread (&it->replaygain_album_peak, 1, 4, fp) != 4) {
+ goto load_fail;
+ }
+ if (fread (&it->replaygain_track_gain, 1, 4, fp) != 4) {
+ goto load_fail;
+ }
+ if (fread (&it->replaygain_track_peak, 1, 4, fp) != 4) {
+ goto load_fail;
+ }
// printf ("loading file %s\n", it->fname);
int16_t nm = 0;
if (fread (&nm, 1, 2, fp) != 2) {
@@ -1284,6 +1282,7 @@ pl_load (const char *fname) {
fclose (fp);
return 0;
load_fail:
+ trace ("playlist load fail!\n");
fclose (fp);
if (it) {
pl_item_free (it);
diff --git a/playlist.h b/playlist.h
index 51bf7124..e27b0df3 100644
--- a/playlist.h
+++ b/playlist.h
@@ -35,15 +35,17 @@ 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)
- float timestart; // start time of cue track, or -1
- float timeend; // end time of cue track, or -1
+ int startsample;
+ int endsample;
float duration; // in seconds
- int startoffset; // offset to seek to skip tags and info-headers (mp3)
- int endoffset; // offset from end of file where music data ends (mp3)
int shufflerating; // sort order for shuffle mode
float playtime; // total playtime
time_t started_timestamp; // result of calling time(NULL)
const char *filetype; // e.g. MP3 or OGG
+ float replaygain_album_gain;
+ float replaygain_album_peak;
+ float replaygain_track_gain;
+ float replaygain_track_peak;
struct playItem_s *next[PL_MAX_ITERATORS]; // next item in linked list
struct playItem_s *prev[PL_MAX_ITERATORS]; // prev item in linked list
struct metaInfo_s *meta; // linked list storing metainfo
@@ -53,8 +55,8 @@ typedef struct playItem_s {
extern playItem_t *playlist_head[PL_MAX_ITERATORS]; // head of linked list
extern playItem_t *playlist_tail[PL_MAX_ITERATORS]; // tail of linked list
-extern playItem_t *playlist_current_ptr; // pointer to a real current playlist item
-extern playItem_t playlist_current; // copy of playlist item being played (stays in memory even if removed from playlist)
+extern playItem_t *playlist_current_ptr; // pointer to a real current playlist item (or NULL)
+
extern int pl_count;
int
@@ -103,13 +105,13 @@ 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);
+//int
+//pl_set_current (playItem_t *it);
// returns -1 if theres no next song, or playlist finished
// reason 0 means "song finished", 1 means "user clicked next"
diff --git a/plugins.c b/plugins.c
index f6ac266b..85067630 100644
--- a/plugins.c
+++ b/plugins.c
@@ -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,
@@ -215,18 +215,18 @@ plug_playback_random (void) {
float
plug_playback_get_pos (void) {
- if (playlist_current.duration <= 0) {
+ if (str_playing_song.duration <= 0) {
return 0;
}
- return streamer_get_playpos () * 100 / playlist_current.duration;
+ return streamer_get_playpos () * 100 / str_playing_song.duration;
}
void
plug_playback_set_pos (float pos) {
- if (playlist_current.duration <= 0) {
+ if (str_playing_song.duration <= 0) {
return;
}
- float t = pos * playlist_current.duration / 100.f;
+ float t = pos * str_playing_song.duration / 100.f;
streamer_set_seek (t);
}
@@ -246,7 +246,7 @@ plug_trigger_event (int ev) {
case DB_EV_SONGFINISHED:
{
DB_event_song_t *pev = malloc (sizeof (DB_event_song_t));
- pev->song = DB_PLAYITEM (&playlist_current);
+ pev->song = DB_PLAYITEM (&str_playing_song);
event = DB_EVENT (pev);
}
break;
diff --git a/plugins/ffap/ffap.c b/plugins/ffap/ffap.c
index edd65ff6..68899578 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
@@ -697,14 +702,19 @@ ffap_init(DB_playItem_t *it)
plugin.info.samplerate = ape_ctx.samplerate;
plugin.info.channels = ape_ctx.channels;
plugin.info.readpos = 0;
- if (it->timeend > 0) {
- timestart = it->timestart;
- timeend = it->timeend;
- plugin.seek (0);
+ if (it->endsample > 0) {
+ startsample = it->startsample;
+ endsample = it->endsample;
+// timestart = it->timestart;
+// timeend = it->timeend;
+ plugin.seek_sample (0);
+ //trace ("start: %d/%f, end: %d/%f\n", startsample, timestart, endsample, timeend);
}
else {
- timestart = 0;
- timeend = it->duration;
+ //timestart = 0;
+ //timeend = it->duration;
+ startsample = 0;
+ endsample = ape_ctx.totalsamples-1;
}
return 0;
}
@@ -1597,7 +1607,7 @@ ape_decode_frame(APEContext *s, void *data, int *data_size)
s->samples -= blockstodecode;
*data_size = (blockstodecode - skip) * 2 * s->channels;
- ape_ctx.currentsample += blockstodecode - skip;
+// ape_ctx.currentsample += blockstodecode - skip;
bytes_used = s->samples ? s->ptr - s->last_ptr : s->packet_remaining;
// shift everything
@@ -1635,7 +1645,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);
@@ -1667,10 +1677,14 @@ ffap_insert (DB_playItem_t *after, const char *fname) {
static int
ffap_read_int16 (char *buffer, int size) {
- int inits = size;
- if (plugin.info.readpos >= (timeend - timestart)) {
- return 0;
+ if (ape_ctx.currentsample + size / (2 * ape_ctx.channels) > endsample) {
+ size = (endsample - ape_ctx.currentsample + 1) * 2 * ape_ctx.channels;
+ trace ("size truncated to %d bytes, cursample=%d, endsample=%d, totalsamples=%d\n", size, ape_ctx.currentsample, endsample, ape_ctx.totalsamples);
+ if (size <= 0) {
+ return 0;
+ }
}
+ int inits = size;
while (size > 0) {
if (ape_ctx.remaining > 0) {
int sz = min (size, ape_ctx.remaining);
@@ -1702,19 +1716,23 @@ ffap_read_int16 (char *buffer, int size) {
}
ape_ctx.remaining -= sz;
}
- plugin.info.readpos = ape_ctx.currentsample / (float)plugin.info.samplerate - timestart;
+ ape_ctx.currentsample += (inits - size) / (2 * ape_ctx.channels);
+ plugin.info.readpos = (ape_ctx.currentsample-startsample) / (float)plugin.info.samplerate;
return inits - size;
}
static int
-ffap_seek (float seconds) {
- seconds += timestart;
- uint32_t newsample = seconds * plugin.info.samplerate;
+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;
@@ -1724,10 +1742,15 @@ 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-startsample)/ape_ctx.samplerate;
return 0;
}
+static int
+ffap_seek (float seconds) {
+ return ffap_seek_sample (seconds * plugin.info.samplerate);
+}
+
static const char *exts[] = { "ape", NULL };
static const char *filetypes[] = { "APE", NULL };
// define plugin interface
@@ -1745,6 +1768,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..7a909792 100644
--- a/plugins/flac/flac.c
+++ b/plugins/flac/flac.c
@@ -24,6 +24,9 @@
static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
@@ -33,6 +36,19 @@ 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 int currentsample;
+
+typedef struct {
+ DB_playItem_t *after;
+ DB_playItem_t *last;
+ const char *fname;
+ int samplerate;
+ int channels;
+ int totalsamples;
+ int bps;
+} cue_cb_data_t;
static FLAC__StreamDecoderWriteStatus
cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const inputbuffer[], void *client_data) {
@@ -71,14 +87,11 @@ cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *fra
static void
cflac_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
- FLAC__uint64 total_samples = metadata->data.stream_info.total_samples;
- int sample_rate = metadata->data.stream_info.sample_rate;
- int channels = metadata->data.stream_info.channels;
- int bps = metadata->data.stream_info.bits_per_sample;
- plugin.info.samplerate = sample_rate;
- plugin.info.channels = channels;
- plugin.info.bps = bps;
- plugin.info.readpos = 0;
+ cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
+ cb->totalsamples = metadata->data.stream_info.total_samples;
+ cb->samplerate = metadata->data.stream_info.sample_rate;
+ cb->channels = metadata->data.stream_info.channels;
+ cb->bps = metadata->data.stream_info.bits_per_sample;
}
static void
@@ -126,24 +139,38 @@ cflac_init (DB_playItem_t *it) {
return -1;
}
FLAC__stream_decoder_set_md5_checking(decoder, 0);
- status = FLAC__stream_decoder_init_file(decoder, it->fname, cflac_write_callback, cflac_metadata_callback, cflac_error_callback, NULL);
+ cue_cb_data_t cb;
+ status = FLAC__stream_decoder_init_file(decoder, it->fname, cflac_write_callback, cflac_metadata_callback, cflac_error_callback, &cb);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
cflac_free ();
return -1;
}
- plugin.info.samplerate = -1;
+ //plugin.info.samplerate = -1;
if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder)) {
cflac_free ();
return -1;
}
+ plugin.info.samplerate = cb.samplerate;
+ plugin.info.channels = cb.channels;
+ plugin.info.bps = cb.bps;
+ plugin.info.readpos = 0;
if (plugin.info.samplerate == -1) { // not a FLAC stream
cflac_free ();
return -1;
}
- timestart = it->timestart;
- timeend = it->timeend;
- if (timeend > timestart || timeend < 0) {
- plugin.seek (0);
+ if (it->endsample > 0) {
+ startsample = it->startsample;
+ endsample = it->endsample;
+ if (plugin.seek_sample (0) < 0) {
+ cflac_free ();
+ return -1;
+ }
+ }
+ else {
+ startsample = 0;
+ endsample = cb.totalsamples-1;
+ currentsample = 0;
+ trace ("startsample=%d, endsample=%d, totalsamples=%d\n", startsample, endsample, cb.totalsamples);
}
plugin.info.readpos = 0;
@@ -161,13 +188,14 @@ cflac_free (void) {
static int
cflac_read_int16 (char *bytes, int size) {
- int initsize = size;
- int nsamples = size / (plugin.info.channels * plugin.info.bps / 8);
- if (timeend > timestart) {
- if (plugin.info.readpos + timestart > timeend) {
+ if (size / (2 * plugin.info.channels) + currentsample > endsample) {
+ size = (endsample - currentsample + 1) * 2 * plugin.info.channels;
+ trace ("size truncated to %d bytes, cursample=%d, endsample=%d\n", size, currentsample, endsample);
+ if (size <= 0) {
return 0;
}
}
+ int initsize = size;
do {
if (remaining) {
int s = size * 2;
@@ -184,12 +212,8 @@ cflac_read_int16 (char *bytes, int size) {
memmove (buffer, &buffer[sz], remaining-sz);
}
remaining -= sz;
+ currentsample += sz / (4 * plugin.info.channels);
plugin.info.readpos += (float)sz / (plugin.info.channels * plugin.info.samplerate * sizeof (float));
- if (timeend > timestart) {
- if (plugin.info.readpos + timestart > timeend) {
- break;
- }
- }
}
if (!size) {
break;
@@ -208,9 +232,10 @@ cflac_read_int16 (char *bytes, int size) {
static int
cflac_read_float32 (char *bytes, int size) {
int initsize = size;
- int nsamples = size / (plugin.info.channels * plugin.info.bps / 8);
- if (timeend > timestart) {
- if (plugin.info.readpos + timestart > timeend) {
+ if (size / (4 * plugin.info.channels) + currentsample > endsample) {
+ size = (endsample - currentsample + 1) * 4 * plugin.info.channels;
+ trace ("size truncated to %d bytes, cursample=%d, endsample=%d\n", size, currentsample, endsample);
+ if (size <= 0) {
return 0;
}
}
@@ -224,12 +249,8 @@ cflac_read_float32 (char *bytes, int size) {
memmove (buffer, &buffer[sz], remaining-sz);
}
remaining -= sz;
+ currentsample += sz / (4 * plugin.info.channels);
plugin.info.readpos += (float)sz / (plugin.info.channels * plugin.info.samplerate * sizeof (int32_t));
- if (timeend > timestart) {
- if (plugin.info.readpos + timestart > timeend) {
- break;
- }
- }
}
if (!size) {
break;
@@ -246,16 +267,22 @@ cflac_read_float32 (char *bytes, int size) {
}
static int
-cflac_seek (float time) {
- time += timestart;
- if (!FLAC__stream_decoder_seek_absolute (decoder, (FLAC__uint64)(time * plugin.info.samplerate))) {
+cflac_seek_sample (int sample) {
+ sample += startsample;
+ if (!FLAC__stream_decoder_seek_absolute (decoder, (FLAC__uint64)(sample))) {
return -1;
}
remaining = 0;
- plugin.info.readpos = time - timestart;
+ currentsample = sample;
+ plugin.info.readpos = (float)sample / plugin.info.samplerate - timestart;
return 0;
}
+static int
+cflac_seek (float time) {
+ return cflac_seek_sample (time * plugin.info.samplerate);
+}
+
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) {
@@ -264,15 +291,6 @@ cflac_init_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
-typedef struct {
- DB_playItem_t *after;
- DB_playItem_t *last;
- const char *fname;
- int samplerate;
- int nchannels;
- float duration;
-} cue_cb_data_t;
-
static void
cflac_init_cue_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
if (cflac_init_stop_decoding) {
@@ -281,68 +299,11 @@ cflac_init_cue_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC_
cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
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;
- }
- // {{{ 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;
- }
+ cb->channels = 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;
}
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++) {
const FLAC__StreamMetadata_VorbisComment_Entry *c = &vc->comments[i];
@@ -351,7 +312,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);
}
}
}
@@ -394,6 +355,21 @@ cflac_init_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__Str
else if (!strncasecmp (s, "DATE=", 5)) {
deadbeef->pl_add_meta (it, "date", s + 5);
}
+ else if (!strncasecmp (s, "replaygain_album_gain=", 22)) {
+ it->replaygain_album_gain = atof (s + 22);
+ }
+ else if (!strncasecmp (s, "replaygain_album_peak=", 22)) {
+ it->replaygain_album_peak = atof (s + 22);
+ }
+ else if (!strncasecmp (s, "replaygain_track_gain=", 22)) {
+ it->replaygain_track_gain = atof (s + 22);
+ }
+ else if (!strncasecmp (s, "replaygain_track_peak=", 22)) {
+ it->replaygain_track_peak = atof (s + 22);
+ }
+ else {
+ trace ("found flac meta: %s\n", s);
+ }
}
}
if (!title_added) {
@@ -463,10 +439,8 @@ 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;
return cue_after;
}
decoder = FLAC__stream_decoder_new();
@@ -480,9 +454,6 @@ cflac_insert (DB_playItem_t *after, const char *fname) {
it = deadbeef->pl_item_alloc ();
it->decoder = &plugin;
it->fname = strdup (fname);
- it->tracknum = 0;
- it->timestart = 0;
- it->timeend = 0;
status = FLAC__stream_decoder_init_file (decoder, fname, cflac_init_write_callback, cflac_init_metadata_callback, cflac_init_error_callback, it);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK || cflac_init_stop_decoding) {
goto cflac_insert_fail;
@@ -527,6 +498,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/lastfm/lastfm.c b/plugins/lastfm/lastfm.c
index 030ca9a9..77ae5657 100644
--- a/plugins/lastfm/lastfm.c
+++ b/plugins/lastfm/lastfm.c
@@ -23,6 +23,9 @@
#include <curl/curl.h>
#include "../../deadbeef.h"
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
#define LFM_TESTMODE 0
#define LFM_IGNORE_RULES 0
#define LFM_NOSEND 0
@@ -69,7 +72,7 @@ lastfm_curl_res (void *ptr, size_t size, size_t nmemb, void *stream)
{
int len = size * nmemb;
if (lfm_reply_sz + len >= MAX_REPLY) {
- fprintf (stderr, "reply is too large. stopping.\n");
+ trace ("reply is too large. stopping.\n");
return 0;
}
memcpy (lfm_reply + lfm_reply_sz, ptr, len);
@@ -77,17 +80,17 @@ lastfm_curl_res (void *ptr, size_t size, size_t nmemb, void *stream)
// char s[size*nmemb+1];
// memcpy (s, ptr, size*nmemb);
// s[size*nmemb] = 0;
-// fprintf (stderr, "got from net: %s\n", s);
+// trace ("got from net: %s\n", s);
return len;
}
static int
curl_req_send (const char *req, const char *post) {
- fprintf (stderr, "sending request: %s\n", req);
+ trace ("sending request: %s\n", req);
CURL *curl;
curl = curl_easy_init ();
if (!curl) {
- fprintf (stderr, "lastfm: failed to init curl\n");
+ trace ("lastfm: failed to init curl\n");
return -1;
}
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
@@ -107,7 +110,7 @@ curl_req_send (const char *req, const char *post) {
lfm_reply[lfm_reply_sz] = 0;
}
if (status != 0) {
- fprintf (stderr, "curl request failed, err:\n%s\n", lfm_err);
+ trace ("curl request failed, err:\n%s\n", lfm_err);
}
return status;
}
@@ -148,7 +151,7 @@ auth (void) {
p++;
}
*p = 0;
- fprintf (stderr, "scrobbler auth failed, response: %s\n", lfm_reply);
+ trace ("scrobbler auth failed, response: %s\n", lfm_reply);
goto fail;
}
uint8_t *p = lfm_reply + 2;
@@ -158,7 +161,7 @@ auth (void) {
}
// get session
if (!*p) {
- fprintf (stderr, "unrecognized scrobbler reply:\n%s\n", lfm_reply);
+ trace ("unrecognized scrobbler reply:\n%s\n", lfm_reply);
goto fail;
}
uint8_t *end = p+1;
@@ -166,12 +169,12 @@ auth (void) {
end++;
}
if (end-p > 32) {
- fprintf (stderr, "scrobbler session id is invalid length. probably plugin needs fixing.\n");
+ trace ("scrobbler session id is invalid length. probably plugin needs fixing.\n");
goto fail;
}
strncpy (lfm_sess, p, 32);
lfm_sess[32] = 0;
- fprintf (stderr, "obtained scrobbler session: %s\n", lfm_sess);
+ trace ("obtained scrobbler session: %s\n", lfm_sess);
p = end;
// skip whitespace
while (*p && *p < 0x20) {
@@ -179,7 +182,7 @@ auth (void) {
}
// get nowplaying url
if (!*p) {
- fprintf (stderr, "unrecognized scrobbler reply:\n%s\n", lfm_reply);
+ trace ("unrecognized scrobbler reply:\n%s\n", lfm_reply);
goto fail;
}
end = p+1;
@@ -187,12 +190,12 @@ auth (void) {
end++;
}
if (end - p > sizeof (lfm_nowplaying_url)-1) {
- fprintf (stderr, "scrobbler nowplaying url is too long:\n", lfm_reply);
+ trace ("scrobbler nowplaying url is too long:\n", lfm_reply);
goto fail;
}
strncpy (lfm_nowplaying_url, p, end-p);
lfm_nowplaying_url[end-p] = 0;
- fprintf (stderr, "obtained scrobbler nowplaying url: %s\n", lfm_nowplaying_url);
+ trace ("obtained scrobbler nowplaying url: %s\n", lfm_nowplaying_url);
p = end;
// skip whitespace
while (*p && *p < 0x20) {
@@ -200,7 +203,7 @@ auth (void) {
}
// get submission url
if (!*p) {
- fprintf (stderr, "unrecognized scrobbler reply:\n%s\n", lfm_reply);
+ trace ("unrecognized scrobbler reply:\n%s\n", lfm_reply);
goto fail;
}
end = p+1;
@@ -208,12 +211,12 @@ auth (void) {
end++;
}
if (end - p > sizeof (lfm_submission_url)-1) {
- fprintf (stderr, "scrobbler submission url is too long:\n", lfm_reply);
+ trace ("scrobbler submission url is too long:\n", lfm_reply);
goto fail;
}
strncpy (lfm_submission_url, p, end-p);
lfm_submission_url[end-p] = 0;
- fprintf (stderr, "obtained scrobbler submission url: %s\n", lfm_submission_url);
+ trace ("obtained scrobbler submission url: %s\n", lfm_submission_url);
p = end;
}
else {
@@ -261,18 +264,18 @@ static int
lfm_uri_encode (char *out, int outl, const char *str) {
int l = outl;
static const char echars[] = " ;/?:@=#&";
- //fprintf (stderr, "lfm_uri_encode %p %d %s\n", out, outl, str);
+ //trace ("lfm_uri_encode %p %d %s\n", out, outl, str);
while (*str) {
if (outl <= 1) {
- //fprintf (stderr, "no space left for 1 byte in buffer\n");
+ //trace ("no space left for 1 byte in buffer\n");
return -1;
}
if (strchr (echars, *str)) {
if (outl <= 3) {
- //fprintf (stderr, "no space left for 3 bytes in the buffer\n");
+ //trace ("no space left for 3 bytes in the buffer\n");
return -1;
}
- //fprintf (stderr, "adding escaped value for %c\n", *str);
+ //trace ("adding escaped value for %c\n", *str);
snprintf (out, outl, "%%%02x", (int)*str);
outl -= 3;
str++;
@@ -325,7 +328,7 @@ lfm_add_keyvalue_uri_encoded (char **out, int *outl, const char *key, const char
static int
lfm_format_uri (int subm, DB_playItem_t *song, char *out, int outl) {
if (subm > 50) {
- fprintf (stderr, "lastfm: it's only allowed to send up to 50 submissions at once (got idx=%d)\n", subm);
+ trace ("lastfm: it's only allowed to send up to 50 submissions at once (got idx=%d)\n", subm);
return -1;
}
int sz = outl;
@@ -353,37 +356,37 @@ lfm_format_uri (int subm, DB_playItem_t *song, char *out, int outl) {
}
if (lfm_fetch_song_info (song, &a, &t, &b, &l, &n, &m) == 0) {
-// fprintf (stderr, "playtime: %f\nartist: %s\ntitle: %s\nalbum: %s\nduration: %f\ntracknum: %s\n---\n", song->playtime, a, t, b, l, n);
+// trace ("playtime: %f\nartist: %s\ntitle: %s\nalbum: %s\nduration: %f\ntracknum: %s\n---\n", song->playtime, a, t, b, l, n);
}
else {
-// fprintf (stderr, "file %s doesn't have enough tags to submit to last.fm\n", song->fname);
+// trace ("file %s doesn't have enough tags to submit to last.fm\n", song->fname);
return -1;
}
if (lfm_add_keyvalue_uri_encoded (&out, &outl, ka, a) < 0) {
-// fprintf (stderr, "failed to add %s=%s\n", ka, a);
+// trace ("failed to add %s=%s\n", ka, a);
return -1;
}
if (lfm_add_keyvalue_uri_encoded (&out, &outl, kt, t) < 0) {
-// fprintf (stderr, "failed to add %s=%s\n", kt, t);
+// trace ("failed to add %s=%s\n", kt, t);
return -1;
}
if (lfm_add_keyvalue_uri_encoded (&out, &outl, kb, b) < 0) {
-// fprintf (stderr, "failed to add %s=%s\n", kb, b);
+// trace ("failed to add %s=%s\n", kb, b);
return -1;
}
if (lfm_add_keyvalue_uri_encoded (&out, &outl, kn, n) < 0) {
-// fprintf (stderr, "failed to add %s=%s\n", kn, n);
+// trace ("failed to add %s=%s\n", kn, n);
return -1;
}
if (lfm_add_keyvalue_uri_encoded (&out, &outl, km, m) < 0) {
-// fprintf (stderr, "failed to add %s=%s\n", km, m);
+// trace ("failed to add %s=%s\n", km, m);
return -1;
}
int processed;
processed = snprintf (out, outl, "%s=%d&", kl, (int)l);
if (processed > outl) {
-// fprintf (stderr, "failed to add %s=%d\n", kl, (int)l);
+// trace ("failed to add %s=%d\n", kl, (int)l);
return -1;
}
out += processed;
@@ -391,7 +394,7 @@ lfm_format_uri (int subm, DB_playItem_t *song, char *out, int outl) {
if (subm >= 0) {
processed = snprintf (out, outl, "i[%d]=%d&o[%d]=P&r[%d]=&", subm, (int)song->started_timestamp, subm, subm);
if (processed > outl) {
-// fprintf (stderr, "failed to add i[%d]=%d&o[%d]=P&r[%d]=&\n", subm, (int)song->started_timestamp, subm, subm);
+// trace ("failed to add i[%d]=%d&o[%d]=P&r[%d]=&\n", subm, (int)song->started_timestamp, subm, subm);
return -1;
}
out += processed;
@@ -407,7 +410,7 @@ lastfm_songstarted (DB_event_song_t *ev, uintptr_t data) {
if (lfm_format_uri (-1, ev->song, lfm_nowplaying, sizeof (lfm_nowplaying)) < 0) {
lfm_nowplaying[0] = 0;
}
-// fprintf (stderr, "%s\n", lfm_nowplaying);
+// trace ("%s\n", lfm_nowplaying);
deadbeef->mutex_unlock (lfm_mutex);
if (lfm_nowplaying[0]) {
deadbeef->cond_signal (lfm_cond);
@@ -418,29 +421,34 @@ lastfm_songstarted (DB_event_song_t *ev, uintptr_t data) {
static int
lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) {
+ trace ("lfm songfinished\n");
#if !LFM_IGNORE_RULES
// check submission rules
+ // duration must be >= 30 sec
+ if (ev->song->duration < 30) {
+ trace ("song duration is %f seconds. not eligible for submission\n", ev->song->duration);
+ return 0;
+ }
// must be played for >=240sec of half the total time
if (ev->song->playtime < 240 && ev->song->playtime < ev->song->duration/2) {
+ trace ("song playtime=%f seconds. not eligible for submission\n", ev->song->playtime);
return 0;
}
- // duration must be >= 30 sec
- if (ev->song->duration < 30) {
- return 0;
- }
#endif
if (!deadbeef->pl_find_meta (ev->song, "artist")
|| !deadbeef->pl_find_meta (ev->song, "title")
// || !deadbeef->pl_find_meta (ev->song, "album")
) {
+ trace ("lfm: not enough metadata for submission, artist=%s, title=%s, album=%s\n", deadbeef->pl_find_meta (ev->song, "artist"), deadbeef->pl_find_meta (ev->song, "title"), deadbeef->pl_find_meta (ev->song, "album"));
return 0;
}
deadbeef->mutex_lock (lfm_mutex);
// find free place in queue
for (int i = 0; i < LFM_SUBMISSION_QUEUE_SIZE; i++) {
if (!lfm_subm_queue[i]) {
+ trace ("lfm: song is now in queue for submission\n");
lfm_subm_queue[i] = deadbeef->pl_item_alloc ();
deadbeef->pl_item_copy (lfm_subm_queue[i], ev->song);
break;
@@ -455,11 +463,11 @@ lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) {
static void
lfm_send_nowplaying (void) {
if (auth () < 0) {
- fprintf (stderr, "auth failed! nowplaying cancelled.\n");
+ trace ("auth failed! nowplaying cancelled.\n");
lfm_nowplaying[0] = 0;
return;
}
- fprintf (stderr, "auth successful! setting nowplaying\n");
+ trace ("auth successful! setting nowplaying\n");
char s[100];
snprintf (s, sizeof (s), "s=%s&", lfm_sess);
int l = strlen (lfm_nowplaying);
@@ -469,23 +477,23 @@ lfm_send_nowplaying (void) {
int status = curl_req_send (lfm_nowplaying_url, lfm_nowplaying);
if (!status) {
if (strncmp (lfm_reply, "OK", 2)) {
- fprintf (stderr, "nowplaying failed, response:\n%s\n", lfm_reply);
+ trace ("nowplaying failed, response:\n%s\n", lfm_reply);
if (!strncmp (lfm_reply, "BADSESSION", 7)) {
- fprintf (stderr, "got badsession; trying to restore session...\n");
+ trace ("got badsession; trying to restore session...\n");
lfm_sess[0] = 0;
curl_req_cleanup ();
if (auth () < 0) {
- fprintf (stderr, "fail!\n");
+ trace ("fail!\n");
break; // total fail
}
- fprintf (stderr, "success! retrying send nowplaying...\n");
+ trace ("success! retrying send nowplaying...\n");
snprintf (s, sizeof (s), "s=%s&", lfm_sess);
strcpy (lfm_nowplaying+l, s);
continue; // retry with new session
}
}
else {
- fprintf (stderr, "nowplaying success! response:\n%s\n", lfm_reply);
+ trace ("nowplaying success! response:\n%s\n", lfm_reply);
}
}
curl_req_cleanup ();
@@ -497,6 +505,7 @@ lfm_send_nowplaying (void) {
static void
lfm_send_submissions (void) {
+ trace ("lfm_send_submissions\n");
int i;
char req[1024*50];
int idx = 0;
@@ -508,6 +517,7 @@ lfm_send_submissions (void) {
if (lfm_subm_queue[i]) {
res = lfm_format_uri (idx, lfm_subm_queue[i], r, len);
if (res < 0) {
+ trace ("lfm: failed to format uri\n");
return;
}
len -= res;
@@ -526,28 +536,28 @@ lfm_send_submissions (void) {
if (res > len) {
return;
}
- fprintf (stderr, "submission req string:\n%s\n", req);
+ trace ("submission req string:\n%s\n", req);
#if !LFM_NOSEND
for (int attempts = 2; attempts > 0; attempts--) {
int status = curl_req_send (lfm_submission_url, req);
if (!status) {
if (strncmp (lfm_reply, "OK", 2)) {
- fprintf (stderr, "submission failed, response:\n%s\n", lfm_reply);
+ trace ("submission failed, response:\n%s\n", lfm_reply);
if (!strncmp (lfm_reply, "BADSESSION", 7)) {
- fprintf (stderr, "got badsession; trying to restore session...\n");
+ trace ("got badsession; trying to restore session...\n");
lfm_sess[0] = 0;
curl_req_cleanup ();
if (auth () < 0) {
- fprintf (stderr, "fail!\n");
+ trace ("fail!\n");
break; // total fail
}
- fprintf (stderr, "success! retrying send nowplaying...\n");
+ trace ("success! retrying send nowplaying...\n");
res = snprintf (r, len, "s=%s&", lfm_sess);
continue; // retry with new session
}
}
else {
- fprintf (stderr, "submission successful, response:\n%s\n", lfm_reply);
+ trace ("submission successful, response:\n%s\n", lfm_reply);
deadbeef->mutex_lock (lfm_mutex);
for (i = 0; i < LFM_SUBMISSION_QUEUE_SIZE; i++) {
if (lfm_subm_queue[i]) {
@@ -561,19 +571,29 @@ lfm_send_submissions (void) {
curl_req_cleanup ();
break;
}
+#else
+ trace ("submission successful (NOSEND=1):\n");
+ deadbeef->mutex_lock (lfm_mutex);
+ for (i = 0; i < LFM_SUBMISSION_QUEUE_SIZE; i++) {
+ if (lfm_subm_queue[i]) {
+ deadbeef->pl_item_free (lfm_subm_queue[i]);
+ lfm_subm_queue[i] = NULL;
+ }
+ }
+ deadbeef->mutex_unlock (lfm_mutex);
#endif
}
static void
lfm_thread (uintptr_t ctx) {
- //fprintf (stderr, "lfm_thread started\n");
+ //trace ("lfm_thread started\n");
for (;;) {
deadbeef->cond_wait (lfm_cond, lfm_mutex);
- fprintf (stderr, "cond signalled!\n");
+ trace ("cond signalled!\n");
if (lfm_stopthread) {
deadbeef->mutex_unlock (lfm_mutex);
deadbeef->cond_signal (lfm_cond);
- fprintf (stderr, "lfm_thread end\n");
+ trace ("lfm_thread end\n");
return;
}
deadbeef->mutex_unlock (lfm_mutex);
@@ -615,9 +635,9 @@ auth_v2 (void) {
if (end) {
*end = 0;
snprintf (msg, sizeof (msg), "http://www.last.fm/api/auth/?api_key=%s&token=%s", LASTFM_API_KEY, token);
- fprintf (stderr, "Dear user. Please visit this URL and authenticate deadbeef. Thanks.\n");
+ trace ("Dear user. Please visit this URL and authenticate deadbeef. Thanks.\n");
- fprintf (stderr, "%s\n", msg);
+ trace ("%s\n", msg);
strncpy (lfm_token, token, 32);
lfm_token[32] = 0;
}
@@ -645,16 +665,16 @@ auth_v2 (void) {
*end = 0;
char config[1024];
snprintf (config, sizeof (config), "%s/.config/deadbeef/lastfmv2", getenv ("HOME"));
- fprintf (stderr, "got session key %s\n", sess);
+ trace ("got session key %s\n", sess);
FILE *fp = fopen (config, "w+b");
if (!fp) {
- fprintf (stderr, "lastfm: failed to write config file %s\n", config);
+ trace ("lastfm: failed to write config file %s\n", config);
curl_req_cleanup ();
return -1;
}
if (fwrite (sess, 1, 32, fp) != 32) {
fclose (fp);
- fprintf (stderr, "lastfm: failed to write config file %s\n", config);
+ trace ("lastfm: failed to write config file %s\n", config);
curl_req_cleanup ();
return -1;
}
@@ -662,7 +682,7 @@ auth_v2 (void) {
strcpy (lfm_sess, sess);
}
}
-// fprintf (stderr, "reply: %s\n", lfm_reply);
+// trace ("reply: %s\n", lfm_reply);
}
curl_req_cleanup ();
if (lfm_sess[0]) {
@@ -708,16 +728,16 @@ lastfm_start (void) {
snprintf (config, 1024, "%s/lastfm", deadbeef->get_config_dir ());
FILE *fp = fopen (config, "rt");
if (!fp) {
- fprintf (stderr, "lastfm: failed open %s\n", config);
+ trace ("lastfm: failed open %s\n", config);
return -1;
}
if (!fgets (lfm_user, 50, fp)) {
- fprintf (stderr, "lastfm: failed to read login from %s\n", config);
+ trace ("lastfm: failed to read login from %s\n", config);
fclose (fp);
return -1;
}
if (!fgets (lfm_pass, 50, fp)) {
- fprintf (stderr, "lastfm: failed to read pass from %s\n", config);
+ trace ("lastfm: failed to read pass from %s\n", config);
fclose (fp);
return -1;
}
@@ -745,7 +765,7 @@ lastfm_start (void) {
static int
lastfm_stop (void) {
- //fprintf (stderr, "lastfm_stop\n");
+ //trace ("lastfm_stop\n");
deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGSTARTED, DB_CALLBACK (lastfm_songstarted), 0);
deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGFINISHED, DB_CALLBACK (lastfm_songfinished), 0);
lfm_stopthread = 1;
diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c
index 74847f37..70de0e56 100644
--- a/plugins/mpgmad/mpgmad.c
+++ b/plugins/mpgmad/mpgmad.c
@@ -22,6 +22,9 @@
#include <stdlib.h>
#include "../../deadbeef.h"
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
@@ -44,16 +47,17 @@ static DB_functions_t *deadbeef;
#define LAME_VBR3 5
#define LAME_VBR4 6
+// xing header flags
+#define FRAMES_FLAG 0x0001
+#define BYTES_FLAG 0x0002
+#define TOC_FLAG 0x0004
+#define VBR_SCALE_FLAG 0x0008
+
typedef struct {
FILE *file;
- // cuesheet info
- float timestart;
- float timeend;
-
- // input buffer, for MPEG data
- // FIXME: this should go away if reading happens per-frame
- mad_timer_t timer;
+// // input buffer, for MPEG data
+// // FIXME: this should go away if reading happens per-frame
char input[READBUFFER];
int remaining;
@@ -77,25 +81,15 @@ typedef struct {
int bitspersample;
int channels;
float duration;
+ int currentsample;
+ int totalsamples;
+ int skipsamples;
int startoffset;
int endoffset;
-#if 0
- // only for xing/lame header
- uint32_t frames;
- uint32_t flags;
- uint32_t vbr_scale;
- uint32_t bytes;
- uint8_t lame_revision;
- uint8_t vbrmethod;
- uint8_t vbrbitrate;
- uint8_t mp3gain;
- float peak_signal_amp;
- uint16_t radio_replay_gain;
- uint16_t audiophile_replay_gain;
- uint16_t delay_start;
- uint16_t delay_end;
- uint32_t musiclength;
-#endif
+ int startsample;
+ int endsample;
+ int startdelay;
+ int enddelay;
} buffer_t;
static buffer_t buffer;
@@ -106,101 +100,6 @@ static struct mad_synth synth;
static int
cmp3_decode (void);
-static int
-cmp3_scan_stream (buffer_t *buffer, float position);
-
-static int
-cmp3_init (DB_playItem_t *it) {
- memset (&buffer, 0, sizeof (buffer));
- buffer.file = fopen (it->fname, "rb");
- if (!buffer.file) {
- return -1;
- }
- buffer.startoffset = it->startoffset;
- buffer.endoffset = it->endoffset;
- buffer.remaining = 0;
- //buffer.output = NULL;
- buffer.readsize = 0;
- buffer.cachefill = 0;
- buffer.cachepos = 0;
- plugin.info.readpos = 0;
- mad_timer_reset(&buffer.timer);
-
-// fseek (buffer.file, buffer.startoffset, SEEK_SET);
- if (it->timeend > 0) {
- buffer.timestart = it->timestart;
- buffer.timeend = it->timeend;
- // that comes from cue, don't calc duration, just seek and play
- cmp3_scan_stream (&buffer, it->timestart);
- mad_timer_reset(&buffer.timer);
- }
- else {
- it->duration = cmp3_scan_stream (&buffer, -1); // scan entire stream, calc duration
- buffer.timestart = 0;
- buffer.timeend = it->duration;
- fseek (buffer.file, buffer.startoffset, SEEK_SET);
- }
- if (buffer.samplerate == 0) {
- //fprintf (stderr, "bad mpeg file: %f\n", it->fname);
- fclose (buffer.file);
- return -1;
- }
- plugin.info.bps = buffer.bitspersample;
- plugin.info.samplerate = buffer.samplerate;
- plugin.info.channels = buffer.channels;
-
- mad_stream_init(&stream);
- mad_frame_init(&frame);
- mad_synth_init(&synth);
-
- return 0;
-}
-
-/****************************************************************************
- * Converts a sample from libmad's fixed point number format to a signed *
- * short (16 bits). *
- ****************************************************************************/
-static inline int16_t
-MadFixedToSshort(mad_fixed_t Fixed)
-{
- /* A fixed point number is formed of the following bit pattern:
- *
- * SWWWFFFFFFFFFFFFFFFFFFFFFFFFFFFF
- * MSB LSB
- * S ==> Sign (0 is positive, 1 is negative)
- * W ==> Whole part bits
- * F ==> Fractional part bits
- *
- * This pattern contains MAD_F_FRACBITS fractional bits, one
- * should alway use this macro when working on the bits of a fixed
- * point number. It is not guaranteed to be constant over the
- * different platforms supported by libmad.
- *
- * The signed short value is formed, after clipping, by the least
- * significant whole part bit, followed by the 15 most significant
- * fractional part bits. Warning: this is a quick and dirty way to
- * compute the 16-bit number, madplay includes much better
- * algorithms.
- */
-
- /* Clipping */
- if(Fixed>=MAD_F_ONE)
- return(32767);
- if(Fixed<=-MAD_F_ONE)
- return(-32768);
-
- /* Conversion. */
- Fixed=Fixed>>(MAD_F_FRACBITS-15);
- return((signed short)Fixed);
-}
-
-static inline float
-MadFixedToFloat (mad_fixed_t Fixed) {
- return (float)((Fixed) / (float)(1L << MAD_F_FRACBITS));
-}
-
-#define MadErrorString(x) mad_stream_errorstr(x)
-
static uint32_t
extract_i32 (unsigned char *buf)
{
@@ -262,41 +161,31 @@ extract_f32 (unsigned char *buf) {
return f;
}
-// function scans file and calculates duration
-// if position >= 0 -- scanning is stopped after duration is greater than position
+// sample=-1: scan entire stream, calculate precise duration
+// sample=0: read headers/tags, calculate approximate duration
+// sample>0: seek to the frame with the sample, update skipsamples
+// return value: -1 on error
static int
-cmp3_scan_stream (buffer_t *buffer, float position) {
+cmp3_scan_stream (buffer_t *buffer, int sample) {
int nframe = 0;
- int nskipped = 0;
- float duration = 0;
- int nreads = 0;
- int nseeks = 0;
-
- int pos = ftell (buffer->file);
- if (pos <= 0) {
- // try to skip id3v2
- int skip = deadbeef->junk_get_leading_size (buffer->file);
- if (skip > 0) {
- fseek (buffer->file, skip, SEEK_SET);
- }
+ int got_xing_header = 0;
+ buffer->duration = 0;
+ int scansamples = 0;
+ buffer->currentsample = 0;
+ buffer->skipsamples = 0;
+
+ if (sample <= 0) {
+ buffer->totalsamples = 0;
}
for (;;) {
- if (position >= 0 && duration > position) {
- // set decoder timer
- buffer->timer.seconds = (int)duration;
- buffer->timer.fraction = (int)((duration - (float)buffer->timer.seconds)*MAD_TIMER_RESOLUTION);
- return duration;
- }
uint32_t hdr;
uint8_t sync;
- pos = ftell (buffer->file);
+ size_t pos = ftell (buffer->file);
if (fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
- nreads++;
if (sync != 0xff) {
- nskipped++;
continue; // not an mpeg frame
}
else {
@@ -304,9 +193,7 @@ cmp3_scan_stream (buffer_t *buffer, float position) {
if (fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
- nreads++;
if ((sync >> 5) != 7) {
- nskipped++;
continue;
}
}
@@ -316,14 +203,11 @@ cmp3_scan_stream (buffer_t *buffer, float position) {
if (fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
- nreads++;
hdr |= sync << 8;
if (fread (&sync, 1, 1, buffer->file) != 1) {
break; // eof
}
- nreads++;
hdr |= sync;
- nskipped = 0;
// parse header
@@ -434,7 +318,7 @@ cmp3_scan_stream (buffer_t *buffer, float position) {
continue;
}
- if (position != 0 || nframe == 0/* || buffer->version != ver || buffer->layer != layer*/)
+ if (sample != 0 || nframe == 0)
{
buffer->version = ver;
buffer->layer = layer;
@@ -444,10 +328,13 @@ cmp3_scan_stream (buffer_t *buffer, float position) {
buffer->frameduration = dur;
buffer->channels = nchannels;
buffer->bitspersample = 16;
- //fprintf (stderr, "frame %d(@%d) mpeg v%d layer %d bitrate %d samplerate %d packetlength %d framedur %f channels %d\n", nframe, pos, ver, layer, bitrate, samplerate, packetlength, dur, nchannels);
+// fprintf (stderr, "frame %d(@%d) mpeg v%d layer %d bitrate %d samplerate %d packetlength %d framedur %f channels %d\n", nframe, pos, ver, layer, bitrate, samplerate, packetlength, dur, nchannels);
}
- // try to read xing/info tag
- if (position == 0) {
+ // try to read xing/info tag (only on initial scans)
+ if (sample <= 0 && !got_xing_header)
+ {
+ size_t framepos = ftell (buffer->file);
+ trace ("trying to read xing header\n");
if (ver == 1) {
fseek (buffer->file, 32, SEEK_CUR);
}
@@ -467,58 +354,232 @@ cmp3_scan_stream (buffer_t *buffer, float position) {
}
if (!strncmp (xing, magic, 4) || !strncmp (info, magic, 4)) {
+ trace ("xing/info frame found\n");
// read flags
uint32_t flags;
- char buf[4];
+ uint8_t buf[4];
if (fread (buf, 1, 4, buffer->file) != 4) {
return -1; // EOF
}
flags = extract_i32 (buf);
- if (flags & 0x01) {
+ if (flags & FRAMES_FLAG) {
// read number of frames
if (fread (buf, 1, 4, buffer->file) != 4) {
return -1; // EOF
}
uint32_t nframes = extract_i32 (buf);
buffer->duration = (float)nframes * (float)samples_per_frame / (float)samplerate;
+ buffer->totalsamples = nframes * samples_per_frame;
+ trace ("xing totalsamples: %d\n", buffer->totalsamples);
+ buffer->samplerate = samplerate;
+ }
+ if (flags & BYTES_FLAG) {
+ fseek (buffer->file, 4, SEEK_CUR);
+ }
+ if (flags & TOC_FLAG) {
+ fseek (buffer->file, 100, SEEK_CUR);
+ }
+ if (flags & VBR_SCALE_FLAG) {
+ fseek (buffer->file, 4, SEEK_CUR);
+ }
+ // lame header
+ if (fread (buf, 1, 4, buffer->file) != 4) {
+ return -1; // EOF
+ }
+ trace ("tell=%x, %c%c%c%c\n", ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]);
+ if (!memcmp (buf, "LAME", 4)) {
+ trace ("lame header found\n");
+ fseek (buffer->file, 6, SEEK_CUR);
+
+ // FIXME: that can be optimized by single read
+ uint8_t lpf;
+ fread (&lpf, 1, 1, buffer->file);
+ //3 floats: replay gain
+ fread (buf, 1, 4, buffer->file);
+ float rg_peaksignalamp = extract_f32 (buf);
+ fread (buf, 1, 2, buffer->file);
+ uint16_t rg_radio = extract_i16 (buf);
+ fread (buf, 1, 2, buffer->file);
+ uint16_t rg_audiophile = extract_i16 (buf);
+ // skip
+ fseek (buffer->file, 2, SEEK_CUR);
+ fread (buf, 1, 3, buffer->file);
+ uint32_t startdelay = (((uint32_t)buf[0]) << 4) | ((((uint32_t)buf[1]) & 0xf0)>>4);
+ uint32_t enddelay = ((((uint32_t)buf[1])&0x0f)<<8) | ((uint32_t)buf[2]);
+ // skip
+ fseek (buffer->file, 1, SEEK_CUR);
+ // mp3gain
+ uint8_t mp3gain;
+ fread (&mp3gain, 1, 1, buffer->file);
+ // skip
+ fseek (buffer->file, 2, SEEK_CUR);
+ // musiclen
+ fread (buf, 1, 4, buffer->file);
+ uint32_t musiclen = extract_i32 (buf);
+ trace ("lpf: %d, peaksignalamp: %f, radiogain: %d, audiophile: %d, startdelay: %d, enddelay: %d, mp3gain: %d, musiclen: %d\n", lpf, rg_peaksignalamp, rg_radio, rg_audiophile, startdelay, enddelay, mp3gain, musiclen);
+ // skip crc
+ //fseek (buffer->file, 4, SEEK_CUR);
+ buffer->startdelay = startdelay;
+ buffer->enddelay = enddelay;
+ }
+ if (sample <= 0 && (flags&FRAMES_FLAG)) {
+ buffer->totalsamples -= buffer->enddelay;
+ trace ("lame totalsamples: %d\n", buffer->totalsamples);
+ fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ return 0;
+ }
+ }
+ if (sample == 0) {
+ // xing header failed, calculate based on file size
+ fseek (buffer->file, 0, SEEK_END);
+ int sz = ftell (buffer->file) - buffer->startoffset - buffer->endoffset;
+ int nframes = sz / packetlength;
+ buffer->duration = nframes * samples_per_frame / samplerate;
+ buffer->totalsamples = nframes * samples_per_frame;
+ buffer->samplerate = samplerate;
+// trace ("packetlength=%d, fsize=%d, nframes=%d, samples_per_frame=%d, samplerate=%d, duration=%f, totalsamples=%d\n", packetlength, sz, nframes, samples_per_frame, samplerate, buffer->duration, buffer->totalsamples);
+
+ if (sample == 0) {
+ fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
return 0;
}
}
- // xing header failed, calculate based on file size
- fseek (buffer->file, 0, SEEK_END);
- int sz = ftell (buffer->file) - buffer->startoffset - buffer->endoffset;
- int nframes = sz / packetlength;
- buffer->duration = nframes * samples_per_frame / samplerate;
+ fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ got_xing_header = 1;
+ }
+ if (sample >= 0 && scansamples + samples_per_frame >= sample) {
+ fseek (buffer->file, -4, SEEK_CUR);
+ buffer->currentsample = sample;
+ buffer->skipsamples = sample - scansamples;
return 0;
}
+ scansamples += samples_per_frame;
- duration += dur;
+ buffer->duration += dur;
nframe++;
if (packetlength > 0) {
fseek (buffer->file, packetlength-4, SEEK_CUR);
- nseeks++;
}
}
- if (position >= 0 && duration >= position) {
- // set decoder timer
- buffer->timer.seconds = (int)duration;
- buffer->timer.fraction = (int)((duration - (float)buffer->timer.seconds)*MAD_TIMER_RESOLUTION);
- }
if (nframe == 0) {
return -1;
}
- return duration;
+ buffer->totalsamples = scansamples;
+ buffer->duration = buffer->totalsamples / buffer->samplerate;
+ return 0;
}
+
+static int
+cmp3_init (DB_playItem_t *it) {
+ memset (&buffer, 0, sizeof (buffer));
+ buffer.file = fopen (it->fname, "rb");
+ if (!buffer.file) {
+ return -1;
+ }
+ int skip = deadbeef->junk_get_leading_size (buffer.file);
+ if (skip > 0) {
+ fseek(buffer.file, skip, SEEK_SET);
+ }
+ buffer.remaining = 0;
+ //buffer.output = NULL;
+ buffer.readsize = 0;
+ buffer.cachefill = 0;
+ buffer.cachepos = 0;
+ plugin.info.readpos = 0;
+
+ cmp3_scan_stream (&buffer, -1); // scan entire stream, calc duration
+ if (it->endsample > 0) {
+ buffer.startsample = it->startsample;
+ buffer.endsample = it->endsample;
+ // that comes from cue, don't calc duration, just seek and play
+ plugin.seek_sample (0);
+ }
+ else {
+ it->duration = buffer.duration;
+ buffer.startsample = 0;
+ buffer.endsample = buffer.totalsamples-1;
+ buffer.skipsamples = buffer.startdelay;
+ buffer.currentsample = buffer.startdelay;
+ fseek (buffer.file, buffer.startoffset, SEEK_SET);
+ }
+ if (buffer.samplerate == 0) {
+ //fprintf (stderr, "bad mpeg file: %f\n", it->fname);
+ fclose (buffer.file);
+ return -1;
+ }
+ plugin.info.bps = buffer.bitspersample;
+ plugin.info.samplerate = buffer.samplerate;
+ plugin.info.channels = buffer.channels;
+
+ mad_stream_init(&stream);
+ mad_frame_init(&frame);
+ mad_synth_init(&synth);
+
+ return 0;
+}
+
+/****************************************************************************
+ * Converts a sample from libmad's fixed point number format to a signed *
+ * short (16 bits). *
+ ****************************************************************************/
+static inline int16_t
+MadFixedToSshort(mad_fixed_t Fixed)
+{
+ /* A fixed point number is formed of the following bit pattern:
+ *
+ * SWWWFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ * MSB LSB
+ * S ==> Sign (0 is positive, 1 is negative)
+ * W ==> Whole part bits
+ * F ==> Fractional part bits
+ *
+ * This pattern contains MAD_F_FRACBITS fractional bits, one
+ * should alway use this macro when working on the bits of a fixed
+ * point number. It is not guaranteed to be constant over the
+ * different platforms supported by libmad.
+ *
+ * The signed short value is formed, after clipping, by the least
+ * significant whole part bit, followed by the 15 most significant
+ * fractional part bits. Warning: this is a quick and dirty way to
+ * compute the 16-bit number, madplay includes much better
+ * algorithms.
+ */
+
+ /* Clipping */
+ if(Fixed>=MAD_F_ONE)
+ return(32767);
+ if(Fixed<=-MAD_F_ONE)
+ return(-32768);
+
+ /* Conversion. */
+ Fixed=Fixed>>(MAD_F_FRACBITS-15);
+ return((signed short)Fixed);
+}
+
+static inline float
+MadFixedToFloat (mad_fixed_t Fixed) {
+ return (float)((Fixed) / (float)(1L << MAD_F_FRACBITS));
+}
+
+#define MadErrorString(x) mad_stream_errorstr(x)
+
static int
cmp3_decode (void) {
+ if (buffer.currentsample + buffer.readsize / (4 * buffer.channels) > buffer.endsample) {
+ buffer.readsize = (buffer.endsample - buffer.currentsample + 1) * 4 * buffer.channels;
+ trace ("size truncated to %d bytes, cursample=%d, endsample=%d, totalsamples=%d\n", buffer.readsize, buffer.currentsample, buffer.endsample, buffer.totalsamples);
+ if (buffer.readsize <= 0) {
+ return 0;
+ }
+ }
int eof = 0;
-// char *output = buffer.output;
for (;;) {
if (eof) {
break;
}
+ // FIXME: read single frame here
// read more MPEG data if needed
if(stream.buffer==NULL || stream.error==MAD_ERROR_BUFLEN) {
// copy part of last frame to beginning
@@ -543,28 +604,31 @@ cmp3_decode (void) {
}
bytesread += buffer.remaining;
mad_stream_buffer(&stream,buffer.input,bytesread);
- stream.error=0;
+ if (stream.buffer==NULL) {
+ // check sync bits
+ if (bytes[0] != 0xff || (bytes[1]&(3<<5)) != (3<<5)) {
+ trace ("mpgmad: read didn't start at frame boundary!\ncmp3_scan_stream is broken\n");
+ }
+ }
}
+ stream.error=0;
// decode next frame
if(mad_frame_decode(&frame,&stream))
{
if(MAD_RECOVERABLE(stream.error))
{
- if(stream.error!=MAD_ERROR_LOSTSYNC)
- {
- //fprintf(stderr,"recoverable frame level error (%s)\n",
- // MadErrorString(&stream));
- //fflush(stderr);
+ if(stream.error!=MAD_ERROR_LOSTSYNC) {
+ trace ("mpgmad: recoverable frame level error (%s)\n", MadErrorString(&stream));
}
continue;
}
else {
- if(stream.error==MAD_ERROR_BUFLEN)
+ if(stream.error==MAD_ERROR_BUFLEN) {
continue;
+ }
else
{
- //fprintf(stderr,"unrecoverable frame level error (%s).\n",
- // MadErrorString(&stream));
+ trace ("mpgmad: unrecoverable frame level error (%s).\n", MadErrorString(&stream));
break;
}
}
@@ -572,23 +636,29 @@ cmp3_decode (void) {
plugin.info.samplerate = frame.header.samplerate;
plugin.info.channels = MAD_NCHANNELS(&frame.header);
-
- mad_timer_add(&buffer.timer,frame.header.duration);
mad_synth_frame(&synth,&frame);
-// char *cache = &buffer.cache[(buffer.cachefill + buffer.cachepos) & CACHE_MASK];
int cachepos = (buffer.cachefill + buffer.cachepos) & CACHE_MASK;
- int i;
- for(i=0;i<synth.pcm.length;i++)
+ int len = synth.pcm.length;
+ if (buffer.currentsample + len > buffer.endsample) {
+ len = buffer.endsample - buffer.currentsample + 1;
+ }
+ int i = min (synth.pcm.length, buffer.skipsamples);
+ if (buffer.skipsamples > 0) {
+ buffer.skipsamples -= i;
+ trace ("skipped %d samples\n", i);
+ }
+ buffer.currentsample += len-i;
+ for(;i<len;i++)
{
if (buffer.cachefill >= CACHE_SIZE) {
printf ("cache overflow!\n");
break;
}
- if (buffer.cachefill >= CACHE_SIZE - sizeof (mad_fixed_t)) {
+// if (buffer.cachefill >= CACHE_SIZE - sizeof (mad_fixed_t)) {
// printf ("readsize=%d, pcm.length=%d(%d)\n", buffer.readsize, synth.pcm.length, i);
- }
+// }
assert (buffer.cachefill < CACHE_SIZE - sizeof (mad_fixed_t));
memcpy (buffer.cache+cachepos, &synth.pcm.samples[0][i], sizeof (mad_fixed_t));
cachepos = (cachepos + sizeof (mad_fixed_t)) & CACHE_MASK;
@@ -596,7 +666,7 @@ cmp3_decode (void) {
buffer.readsize -= sizeof (mad_fixed_t);
if (MAD_NCHANNELS(&frame.header) == 2) {
if (buffer.cachefill >= CACHE_SIZE - sizeof (mad_fixed_t)) {
- printf ("readsize=%d, pcm.length=%d(%d), cachefill=%d, cachepos=%d(%d)\n", buffer.readsize, synth.pcm.length, i, buffer.cachefill, buffer.cachepos, cachepos);
+ trace ("readsize=%d, pcm.length=%d(%d), cachefill=%d, cachepos=%d(%d)\n", buffer.readsize, synth.pcm.length, i, buffer.cachefill, buffer.cachepos, cachepos);
}
assert (buffer.cachefill < CACHE_SIZE - sizeof (mad_fixed_t));
memcpy (buffer.cache+cachepos, &synth.pcm.samples[1][i], sizeof (mad_fixed_t));
@@ -605,8 +675,13 @@ cmp3_decode (void) {
buffer.readsize -= sizeof (mad_fixed_t);
}
}
- //printf ("readsize at end of frame: %d\n", buffer.readsize);
- if (buffer.readsize <= 0 || eof) {
+ if (buffer.currentsample > buffer.totalsamples) {
+ trace ("mpgmad: warning: extra samples were read after end of stream\n");
+ }
+ if (buffer.readsize <= 0 || eof || buffer.currentsample > buffer.endsample) {
+ if (buffer.currentsample > buffer.endsample) {
+ trace ("finished at sample %d (%d)\n", buffer.currentsample, buffer.totalsamples);
+ }
break;
}
}
@@ -628,15 +703,12 @@ static int
cmp3_read (char *bytes, int size) {
int result;
int ret = 0;
- if (plugin.info.readpos >= (buffer.timeend - buffer.timestart)) {
- return 0;
- }
int nsamples = size / 2 / plugin.info.channels;
size *= 2; // convert to mad sample size
if (buffer.cachefill < size) {
buffer.readsize = (size - buffer.cachefill);
cmp3_decode ();
- plugin.info.readpos = (float)buffer.timer.seconds + (float)buffer.timer.fraction / MAD_TIMER_RESOLUTION;
+ plugin.info.readpos = (float)(buffer.currentsample-buffer.startsample) / buffer.samplerate;
}
if (buffer.cachefill > 0) {
int sz = min (size, buffer.cachefill);
@@ -666,9 +738,6 @@ cmp3_read (char *bytes, int size) {
buffer.cachepos = 0;
}
}
- if (plugin.info.readpos >= (buffer.timeend - buffer.timestart)) {
- return 0;
- }
return ret;
}
@@ -676,15 +745,12 @@ static int
cmp3_read_float32 (char *bytes, int size) {
int result;
int ret = 0;
- if (plugin.info.readpos >= (buffer.timeend - buffer.timestart)) {
- return 0;
- }
int nsamples = size / 4 / plugin.info.channels;
if (buffer.cachefill < size) {
buffer.readsize = (size - buffer.cachefill);
//printf ("decoding %d bytes using read_float32\n", buffer.readsize);
cmp3_decode ();
- plugin.info.readpos = (float)buffer.timer.seconds + (float)buffer.timer.fraction / MAD_TIMER_RESOLUTION;
+ plugin.info.readpos = (float)(buffer.currentsample - buffer.startsample) / buffer.samplerate;
}
if (buffer.cachefill > 0) {
int sz = min (size, buffer.cachefill);
@@ -713,184 +779,57 @@ cmp3_read_float32 (char *bytes, int size) {
buffer.cachefill = 0;
}
}
- if (plugin.info.readpos >= (buffer.timeend - buffer.timestart)) {
- return 0;
- }
return ret;
}
static int
-cmp3_seek (float time) {
- time += buffer.timestart;
+cmp3_seek_sample (int sample) {
if (!buffer.file) {
return -1;
}
+ sample += buffer.startsample + buffer.startdelay;
+ if (sample > buffer.endsample) {
+ trace ("seek sample %d is beyond end of track (%d)\n", sample, buffer.endsample);
+ return -1; // eof
+ }
// restart file, and load until we hit required pos
+ fseek (buffer.file, 0, SEEK_SET);
+ int skip = deadbeef->junk_get_leading_size (buffer.file);
+ if (skip > 0) {
+ fseek(buffer.file, skip, SEEK_SET);
+ }
mad_synth_finish (&synth);
mad_frame_finish (&frame);
mad_stream_finish (&stream);
- fseek(buffer.file, buffer.startoffset, SEEK_SET);
- mad_stream_init(&stream);
- mad_frame_init(&frame);
- mad_synth_init(&synth);
- mad_timer_reset(&buffer.timer);
+ buffer.remaining = 0;
+ buffer.readsize = 0;
+ buffer.cachefill = 0;
+ buffer.cachepos = 0;
- if (time == 0) {
+ if (sample == 0) {
plugin.info.readpos = 0;
+ buffer.currentsample = 0;
+ buffer.skipsamples = buffer.startdelay;
return 0;
}
- if (cmp3_scan_stream (&buffer, time) == -1) {
+ if (cmp3_scan_stream (&buffer, sample) == -1) {
+ trace ("failed to seek to sample %d\n", sample);
plugin.info.readpos = 0;
return -1;
}
- // fixup timer
- plugin.info.readpos = (float)buffer.timer.seconds + (float)buffer.timer.fraction / MAD_TIMER_RESOLUTION;
- plugin.info.readpos -= buffer.timestart;
- buffer.timer.seconds = (int)plugin.info.readpos;
- buffer.timer.fraction = (plugin.info.readpos - buffer.timer.seconds) * MAD_TIMER_RESOLUTION;
+ mad_stream_init(&stream);
+ mad_frame_init(&frame);
+ mad_synth_init(&synth);
+ plugin.info.readpos = (float)(buffer.currentsample - buffer.startsample) / buffer.samplerate;
return 0;
}
-// {{{ separate xing/lame header reader (unused)
-#if 0
-// read xing/lame header
-// based on Xing example code
-#define FRAMES_FLAG 0x0001
-#define BYTES_FLAG 0x0002
-#define TOC_FLAG 0x0004
-#define VBR_SCALE_FLAG 0x0008
-
-#define FRAMES_AND_BYTES (FRAMES_FLAG | BYTES_FLAG)
-int
-cmp3_read_info_tag (buffer_t *buffer, DB_playItem_t *it, FILE *fp) {
- rewind (fp);
- int h_id, h_mode, h_sr_index, h_ly_index;
- static int sr_table[4] = { 44100, 48000, 32000, -1 };
- uint8_t header[1024];
- if (fread (header, 1, 1024, fp) != 1024) {
- return -1;
- }
- uint8_t *buf = header;
-
- // check sync
- if (buf[0] != 0xff || (buf[1]&(3<<5)) != (3<<5)) {
- printf ("sync bits not set, not a mpeg frame\n");
- return -1;
- }
-
- // get selected MPEG header data
- h_id = (buf[1] >> 3) & 1;
- h_sr_index = (buf[2] >> 2) & 3;
- h_mode = (buf[3] >> 6) & 3;
- h_ly_index = (buf[1] >> 1) & 3;
-
- static int ltbl[] = { -1, 3, 2, 1 };
- int layer = ltbl[h_ly_index];
- printf ("layer%d\n", layer);
- if (layer < 0) {
- printf ("invalid layer\n");
- return -1;
- }
-
- // determine offset of header
- if (h_id) { // mpeg1
- if (h_mode != 3) {
- buf += (32+4);
- }
- else {
- buf += (17+4);
- }
- }
- else { // mpeg2
- if (h_mode != 3) {
- buf += (17+4);
- }
- else {
- buf += (9+4);
- }
- }
-
- int is_vbr = 0;
- if (!strncmp (buf, "Xing", 4)) {
- is_vbr = 1;
- }
- else if (!strncmp (buf, "Info", 4)) {
- is_vbr = 0;
- }
- else {
- return -1; // no xing header found
- }
-
- buf+=4;
-
- if (h_id) {
- buffer->version = 1;
- }
- else {
- buffer->version = 2;
- }
-
- buffer->samplerate = sr_table[h_sr_index];
- if (!h_id) {
- buffer->samplerate >>= 1;
- }
-
- uint32_t flags, frames, bytes, vbr_scale;
- // get flags
- flags = extract_i32 (buf);
- buf+=4;
-
- if (flags & FRAMES_FLAG) {
- frames = extract_i32(buf);
- buf+=4;
- }
- if (flags & BYTES_FLAG) {
- bytes = extract_i32(buf);
- buf+=4;
- }
-
- if (flags & TOC_FLAG) {
- // read toc
- buf+=100;
- }
-
- vbr_scale = -1;
- if (flags & VBR_SCALE_FLAG) {
- vbr_scale = extract_i32(buf);
- buf+=4;
- }
-
- // check for lame header
- if( buf[0] != 'L' ) return 0;
- if( buf[1] != 'A' ) return 0;
- if( buf[2] != 'M' ) return 0;
- if( buf[3] != 'E' ) return 0;
- printf ("found LAME header\n");
- buf += 20;
- buf += 140;
-
- uint8_t vbrmethod = header[0xa5];
- if (vbrmethod & 0xf0) {
- buffer->lame_revision = 1;
- }
- else {
- buffer->lame_revision = 0;
- }
- buffer->vbrmethod = vbrmethod & 0x0f;
-
- buffer->peak_signal_amp = extract_f32 (&header[0xa7]);
- buffer->radio_replay_gain = extract_i16 (&header[0xab]);
- buffer->audiophile_replay_gain = extract_i16 (&header[0xad]);
- buffer->vbrbitrate = header[0xb0];
- buffer->delay_start = (((uint16_t)header[0xb1]) << 4) | (((uint16_t)header[0xb2])>>4);
- buffer->delay_end = ((((uint16_t)header[0xb2]) & 0x0f) << 8) | ((uint16_t)header[0xb3]);
- buffer->mp3gain = header[0xb5];
- buffer->musiclength = extract_i32 (&header[0xb8]);
- return 0;
+static int
+cmp3_seek (float time) {
+ int sample = time * buffer.samplerate;
+ return cmp3_seek_sample (sample);
}
-#endif
-// }}}
static const char *filetypes[] = {
"MPEG 1.0 layer I", "MPEG 1.0 layer II", "MPEG 1.0 layer III", "MPEG 2.0 layer I", "MPEG 2.0 layer II", "MPEG 2.0 layer III", "MPEG 2.5 layer I", "MPEG 2.5 layer II", "MPEG 2.5 layer III", NULL
@@ -905,6 +844,10 @@ cmp3_insert (DB_playItem_t *after, const char *fname) {
buffer_t buffer;
memset (&buffer, 0, sizeof (buffer));
buffer.file = fp;
+ int skip = deadbeef->junk_get_leading_size (buffer.file);
+ if (skip > 0) {
+ fseek(buffer.file, skip, SEEK_SET);
+ }
// calc approx. mp3 duration
int res = cmp3_scan_stream (&buffer, 0);
if (res < 0) {
@@ -952,7 +895,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;
@@ -964,22 +908,15 @@ cmp3_insert (DB_playItem_t *after, const char *fname) {
it->decoder = &plugin;
it->fname = strdup (fname);
+ int apeerr = deadbeef->junk_read_ape (it, fp);
int v2err = deadbeef->junk_read_id3v2 (it, fp);
int v1err = deadbeef->junk_read_id3v1 (it, fp);
- if (v1err >= 0) {
- fseek (fp, -128, SEEK_END);
- }
- else {
- fseek (fp, 0, SEEK_END);
- }
- int apeerr = deadbeef->junk_read_ape (it, fp);
+ fclose (fp);
deadbeef->pl_add_meta (it, "title", NULL);
- it->startoffset = buffer.startoffset;
it->duration = buffer.duration;
it->filetype = ftype;
after = deadbeef->pl_insert_item (after, it);
- fclose (fp);
return after;
}
@@ -1000,10 +937,10 @@ static DB_decoder_t plugin = {
.plugin.website = "http://deadbeef.sf.net",
.init = cmp3_init,
.free = cmp3_free,
-// .read = cmp3_read,
.read_int16 = cmp3_read,
.read_float32 = cmp3_read_float32,
.seek = cmp3_seek,
+ .seek_sample = cmp3_seek_sample,
.insert = cmp3_insert,
.exts = exts,
.id = "stdmpg",
diff --git a/plugins/vorbis/vorbis.c b/plugins/vorbis/vorbis.c
index 6daba52a..b854356a 100644
--- a/plugins/vorbis/vorbis.c
+++ b/plugins/vorbis/vorbis.c
@@ -25,6 +25,12 @@
#endif
#include "../../deadbeef.h"
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
@@ -34,14 +40,14 @@ static vorbis_info *vi;
static int cur_bit_stream;
static float timestart;
static float timeend;
+static int startsample;
+static int endsample;
+static int currentsample;
static void
cvorbis_free (void);
static int
-cvorbis_seek (float time);
-
-static int
cvorbis_init (DB_playItem_t *it) {
file = NULL;
vi = NULL;
@@ -64,14 +70,15 @@ cvorbis_init (DB_playItem_t *it) {
plugin.info.channels = vi->channels;
plugin.info.samplerate = vi->rate;
plugin.info.readpos = 0;
- if (it->timeend > 0) {
- timestart = it->timestart;
- timeend = it->timeend;
- cvorbis_seek (0);
+ currentsample = 0;
+ if (it->endsample > 0) {
+ startsample = it->startsample;
+ endsample = it->endsample;
+ plugin.seek_sample (0);
}
else {
- timestart = 0;
- timeend = it->duration;
+ startsample = 0;
+ endsample = ov_pcm_total (&vorbis_file, -1)-1;
}
return 0;
}
@@ -88,8 +95,12 @@ cvorbis_free (void) {
static int
cvorbis_read (char *bytes, int size) {
- if (plugin.info.readpos >= (timeend - timestart)) {
- return 0;
+ if (currentsample + size / (2 * plugin.info.channels) > endsample) {
+ size = (endsample - currentsample + 1) * 2 * plugin.info.channels;
+ trace ("size truncated to %d bytes, cursample=%d, endsample=%d, totalsamples=%d\n", size, currentsample, endsample, ov_pcm_total (&vorbis_file, -1));
+ if (size <= 0) {
+ return 0;
+ }
}
int initsize = size;
for (;;)
@@ -100,44 +111,50 @@ cvorbis_read (char *bytes, int size) {
endianess = 1;
#endif
long ret=ov_read (&vorbis_file, bytes, size, endianess, 2, 1, &cur_bit_stream);
- if (ret < 0)
+ if (ret <= 0)
{
- break;
- }
- else if (ret == 0) {
+ // error or eof
break;
}
else if (ret < size)
{
+ currentsample += ret / (vi->channels * 2);
size -= ret;
bytes += ret;
}
else {
+ currentsample += ret / (vi->channels * 2);
size = 0;
break;
}
}
plugin.info.readpos = ov_time_tell(&vorbis_file) - timestart;
- if (plugin.info.readpos >= (timeend - timestart)) {
- return 0;
- }
-
return initsize - size;
}
static int
-cvorbis_seek (float time) {
- time += timestart;
+cvorbis_seek_sample (int sample) {
if (!file) {
return -1;
}
- int res = ov_time_seek (&vorbis_file, time);
+ sample += startsample;
+ int res = ov_pcm_seek (&vorbis_file, sample);
if (res != 0 && res != OV_ENOSEEK)
return -1;
+ int tell = ov_pcm_tell (&vorbis_file);
+ if (tell != sample) {
+ fprintf (stderr, "oggvorbis: failed to do sample-accurate seek (%d->%d)\n", sample, tell);
+ }
+ currentsample = sample;
plugin.info.readpos = ov_time_tell(&vorbis_file) - timestart;
return 0;
}
+static int
+cvorbis_seek (float time) {
+ return cvorbis_seek_sample (time * vi->rate);
+}
+
static DB_playItem_t *
cvorbis_insert (DB_playItem_t *after, const char *fname) {
// check for validity
@@ -153,7 +170,8 @@ 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);
+ int totalsamples = ov_pcm_total (&vorbis_file, -1);
+ DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "OggVorbis", totalsamples, vi->rate);
if (cue_after) {
ov_clear (&vorbis_file);
return cue_after;
@@ -184,6 +202,18 @@ cvorbis_insert (DB_playItem_t *after, const char *fname) {
else if (!strncasecmp (vc->user_comments[i], "date=", 5)) {
deadbeef->pl_add_meta (it, "date", vc->user_comments[i] + 5);
}
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_album_gain=", 22)) {
+ it->replaygain_album_gain = atof (vc->user_comments[i] + 22);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_album_peak=", 22)) {
+ it->replaygain_album_peak = atof (vc->user_comments[i] + 22);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_track_gain=", 22)) {
+ it->replaygain_track_gain = atof (vc->user_comments[i] + 22);
+ }
+ else if (!strncasecmp (vc->user_comments[i], "replaygain_track_peak=", 22)) {
+ it->replaygain_track_peak = atof (vc->user_comments[i] + 22);
+ }
}
}
if (!title_added) {
@@ -213,6 +243,7 @@ static DB_decoder_t plugin = {
// vorbisfile can't output float32
// .read_float32 = cvorbis_read_float32,
.seek = cvorbis_seek,
+ .seek_sample = cvorbis_seek_sample,
.insert = cvorbis_insert,
.exts = exts,
.id = "stdogg",
diff --git a/streamer.c b/streamer.c
index e351e41d..dd9d0c14 100644
--- a/streamer.c
+++ b/streamer.c
@@ -34,6 +34,7 @@
#include "conf.h"
#include "plugins.h"
#include "optmath.h"
+#include "volume.h"
static intptr_t streamer_tid;
static SRC_STATE *src;
@@ -44,7 +45,7 @@ static float g_fbuffer[200000]; // hack!
static float g_srcbuffer[200000]; // hack!
static int streaming_terminate;
-#define STREAM_BUFFER_SIZE 200000
+#define STREAM_BUFFER_SIZE (200000*5)
static int streambuffer_fill;
static int bytes_until_next_song = 0;
static char streambuffer[STREAM_BUFFER_SIZE];
@@ -57,6 +58,107 @@ static float seekpos = -1;
static float playpos = 0; // play position of current song
+playItem_t str_playing_song;
+playItem_t str_streaming_song;
+// remember pointers to original instances of playitems
+static playItem_t *orig_playing_song;
+static playItem_t *orig_streaming_song;
+
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
+
+// playlist must call that whenever item was removed
+void
+streamer_song_removed_notify (playItem_t *it) {
+ if (it == orig_playing_song) {
+ orig_playing_song = NULL;
+ }
+ if (it == orig_streaming_song) {
+ orig_streaming_song = NULL;
+ // queue new next song for streaming
+ if (bytes_until_next_song > 0) {
+ streambuffer_fill = bytes_until_next_song;
+ pl_nextsong (0);
+ }
+ }
+}
+
+// that must be called after last sample from str_playing_song was done reading
+static int
+streamer_set_current (playItem_t *it) {
+ trace ("streamer_set_current %p, buns=%d\n", it);
+// if (str_streaming_song.decoder) {
+// trace ("sending songfinished to plugins [1]\n");
+// plug_trigger_event (DB_EV_SONGFINISHED);
+// str_streaming_song.decoder->free ();
+// }
+ pl_item_free (&str_streaming_song);
+ orig_streaming_song = it;
+ if (!it) {
+ return 0;
+ }
+ if (it->decoder) {
+ int ret = it->decoder->init (DB_PLAYITEM (it));
+ trace ("input samplerate: %d\n", it->decoder->info.samplerate);
+ pl_item_copy (&str_streaming_song, it);
+ if (ret < 0) {
+ trace ("decoder->init returned %d\n", ret);
+ return ret;
+ }
+ else {
+ trace ("bps=%d, channels=%d, samplerate=%d\n", it->decoder->info.bps, it->decoder->info.channels, it->decoder->info.samplerate);
+ }
+ streamer_reset (0); // reset SRC
+ }
+ else {
+ trace ("no decoder in playitem!\n");
+ orig_streaming_song = NULL;
+ return -1;
+ }
+ if (bytes_until_next_song == -1) {
+ bytes_until_next_song = 0;
+ }
+ return 0;
+}
+
+#if 0
+static int
+str_set_current (playItem_t *it) {
+ int ret = 0;
+ int from = pl_get_idx_of (playlist_current_ptr);
+ int to = it ? pl_get_idx_of (it) : -1;
+ if (str_playing_song.decoder) {
+ plug_trigger_event (DB_EV_SONGFINISHED);
+ }
+ codec_lock ();
+ if (str_playing_song.decoder) {
+ str_playing_song.decoder->free ();
+ }
+ pl_item_free (&str_playing_song);
+ playlist_current_ptr = it;
+ if (it && it->decoder) {
+ // don't do anything on fail, streamer will take care
+ ret = it->decoder->init (DB_PLAYITEM (it));
+// if (ret < 0) {
+// }
+ }
+ if (playlist_current_ptr) {
+ streamer_reset (0);
+ }
+ if (it) {
+ it->played = 1;
+ it->started_timestamp = time (NULL);
+ pl_item_copy (&str_playing_song, it);
+ }
+ codec_unlock ();
+ if (it) {
+ plug_trigger_event (DB_EV_SONGSTARTED);
+ }
+ messagepump_push (M_SONGCHANGED, 0, from, to);
+ return ret;
+}
+#endif
+
float
streamer_get_playpos (void) {
return playpos;
@@ -64,6 +166,7 @@ streamer_get_playpos (void) {
void
streamer_set_nextsong (int song, int pstate) {
+ trace ("streamer_set_nextsong %d %d\n", song, pstate);
nextsong = song;
nextsong_pstate = pstate;
if (p_isstopped ()) {
@@ -88,7 +191,8 @@ streamer_thread (uintptr_t ctx) {
while (!streaming_terminate) {
struct timeval tm1;
gettimeofday (&tm1, NULL);
- if (nextsong >= 0 && bytes_until_next_song == 0) {
+ if (nextsong >= 0) { // start streaming next song
+ trace ("nextsong=%d\n", nextsong);
int sng = nextsong;
int pstate = nextsong_pstate;
nextsong = -1;
@@ -96,14 +200,14 @@ streamer_thread (uintptr_t ctx) {
codecleft = 0;
codec_unlock ();
if (badsong == sng) {
- //printf ("looped to bad file. stopping...\n");
+ trace ("looped to bad file. stopping...\n");
streamer_set_nextsong (-2, 1);
badsong = -1;
continue;
}
- int ret = pl_set_current (pl_get_for_idx (sng));
+ int ret = streamer_set_current (pl_get_for_idx (sng));
if (ret < 0) {
- //printf ("bad file in playlist, skipping...\n");
+ trace ("bad file in playlist, skipping...\n");
// remember bad song number in case of looping
if (badsong == -1) {
badsong = sng;
@@ -114,7 +218,6 @@ streamer_thread (uintptr_t ctx) {
continue;
}
badsong = -1;
- playpos = 0;
if (pstate == 0) {
p_stop ();
}
@@ -126,10 +229,19 @@ streamer_thread (uintptr_t ctx) {
}
}
else if (nextsong == -2) {
+ trace ("nextsong=-2\n");
nextsong = -1;
p_stop ();
- messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (playlist_current_ptr), -1);
- pl_set_current (NULL);
+ if (str_playing_song.decoder) {
+ trace ("sending songfinished to plugins [1]\n");
+ plug_trigger_event (DB_EV_SONGFINISHED);
+ }
+ messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (orig_playing_song), -1);
+ streamer_set_current (NULL);
+ if (str_playing_song.decoder) {
+ pl_item_free (&str_playing_song);
+ orig_playing_song = NULL;
+ }
continue;
}
else if (p_isstopped ()) {
@@ -137,12 +249,56 @@ streamer_thread (uintptr_t ctx) {
continue;
}
+ if (bytes_until_next_song == 0) {
+ if (!str_streaming_song.fname) {
+ // means last song was deleted during final drain
+ nextsong = -1;
+ p_stop ();
+ messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (playlist_current_ptr), -1);
+ streamer_set_current (NULL);
+ continue;
+ }
+ trace ("bytes_until_next_song=0, starting playback of new song\n");
+ bytes_until_next_song = -1;
+ // plugin will get pointer to str_playing_song
+ if (str_playing_song.decoder) {
+ trace ("sending songfinished to plugins [2]\n");
+ plug_trigger_event (DB_EV_SONGFINISHED);
+ }
+ // copy streaming into playing
+ pl_item_copy (&str_playing_song, &str_streaming_song);
+ int from = orig_playing_song ? pl_get_idx_of (orig_playing_song) : -1;
+ int to = orig_streaming_song ? pl_get_idx_of (orig_streaming_song) : -1;
+ trace ("from=%d, to=%d\n", from, to);
+ orig_playing_song = orig_streaming_song;
+ str_playing_song.played = 1;
+ str_playing_song.started_timestamp = time (NULL);
+ playlist_current_ptr = orig_playing_song;
+ // that is needed for playlist drawing
+ trace ("sending songchanged\n");
+ messagepump_push (M_SONGCHANGED, 0, from, to);
+ // plugin will get pointer to new str_playing_song
+ trace ("sending songstarted to plugins\n");
+ plug_trigger_event (DB_EV_SONGSTARTED);
+ playpos = 0;
+ }
+
if (seekpos >= 0) {
+ trace ("seeking to %f\n", seekpos);
float pos = seekpos;
seekpos = -1;
- if (playlist_current.decoder && playlist_current.decoder->seek (pos) >= 0) {
+
+ if (orig_playing_song != orig_streaming_song) {
+ // restart playing from new position
+ pl_item_free (&str_streaming_song);
+ orig_streaming_song = orig_playing_song;
+ pl_item_copy (&str_streaming_song, orig_streaming_song);
+ bytes_until_next_song = -1;
+ }
+
+ if (str_playing_song.decoder && str_playing_song.decoder->seek (pos) >= 0) {
streamer_lock ();
- playpos = playlist_current.decoder->info.readpos;
+ playpos = str_playing_song.decoder->info.readpos;
streambuffer_fill = 0;
streamer_unlock ();
codec_lock ();
@@ -156,7 +312,7 @@ streamer_thread (uintptr_t ctx) {
int alloc_time = 1000 / (96000 * 4 / 4096);
streamer_lock ();
- if (streambuffer_fill < (STREAM_BUFFER_SIZE-4096) && bytes_until_next_song == 0) {
+ if (streambuffer_fill < (STREAM_BUFFER_SIZE-4096)/* && bytes_until_next_song == 0*/) {
int sz = STREAM_BUFFER_SIZE - streambuffer_fill;
int minsize = 4096;
if (streambuffer_fill < 16384) {
@@ -178,6 +334,7 @@ streamer_thread (uintptr_t ctx) {
if (alloc_time > 0) {
usleep (alloc_time * 1000);
}
+// trace ("fill: %d/%d\n", streambuffer_fill, STREAM_BUFFER_SIZE);
}
if (src) {
@@ -215,6 +372,72 @@ streamer_reset (int full) { // must be called when current song changes by exter
src_reset (src);
}
+int replaygain = 1;
+
+static void
+apply_replay_gain_int16 (playItem_t *it, char *bytes, int size) {
+ if (!replaygain || !conf_replaygain_mode) {
+ return;
+ }
+ int vol = 1000;
+ if (conf_replaygain_mode == 1) {
+ if (it->replaygain_track_gain == 0) {
+ return;
+ }
+ vol = db_to_amp (str_streaming_song.replaygain_track_gain) * 1000;
+ }
+ else if (conf_replaygain_mode == 2) {
+ if (it->replaygain_album_gain == 0) {
+ return;
+ }
+ vol = db_to_amp (str_streaming_song.replaygain_album_gain) * 1000;
+ }
+ int16_t *s = (int16_t*)bytes;
+ for (int j = 0; j < size/2; j++) {
+ int32_t sample = ((int32_t)(*s)) * vol / 1000;
+ if (sample > 0x7fff) {
+ sample = 0x7fff;
+ }
+ else if (sample < -0x8000) {
+ sample = -0x8000;
+ }
+ *s = (int16_t)sample;
+ s++;
+ }
+}
+
+static void
+apply_replay_gain_float32 (playItem_t *it, char *bytes, int size) {
+ if (!replaygain || !conf_replaygain_mode) {
+ return;
+ }
+ float vol = 1;
+ if (conf_replaygain_mode == 1) {
+ if (it->replaygain_track_gain == 0) {
+ return;
+ }
+ vol = db_to_amp (it->replaygain_track_gain);
+ }
+ else if (conf_replaygain_mode == 2) {
+ if (it->replaygain_album_gain == 0) {
+ return;
+ }
+ vol = db_to_amp (it->replaygain_album_gain);
+ }
+ float *s = (float*)bytes;
+ for (int j = 0; j < size/4; j++) {
+ float sample = ((float)*s) * vol;
+ if (sample > 1.f) {
+ sample = 1.f;
+ }
+ else if (sample < -1.f) {
+ sample = -1.f;
+ }
+ *s = sample;
+ s++;
+ }
+}
+
// returns number of bytes been read
static int
streamer_read_async (char *bytes, int size) {
@@ -222,8 +445,9 @@ streamer_read_async (char *bytes, int size) {
for (;;) {
int bytesread = 0;
codec_lock ();
- DB_decoder_t *decoder = playlist_current.decoder;
+ DB_decoder_t *decoder = str_streaming_song.decoder;
if (!decoder) {
+ // means there's nothing left to stream, so just do nothing
codec_unlock ();
break;
}
@@ -236,10 +460,12 @@ streamer_read_async (char *bytes, int size) {
if (decoder->info.channels == 2) {
bytesread = decoder->read_int16 (bytes, size);
codec_unlock ();
+ apply_replay_gain_int16 (&str_streaming_song, bytes, size);
}
else {
bytesread = decoder->read_int16 (g_readbuffer, size/2);
codec_unlock ();
+ apply_replay_gain_int16 (&str_streaming_song, g_readbuffer, size/2);
for (i = 0; i < size/4; i++) {
int16_t sample = (int16_t)(((int32_t)(((int16_t*)g_readbuffer)[i])));
((int16_t*)bytes)[i*2+0] = sample;
@@ -269,6 +495,7 @@ streamer_read_async (char *bytes, int size) {
// }
if (!decoder->read_float32) {
bytesread = decoder->read_int16 (g_readbuffer, nbytes);
+ apply_replay_gain_int16 (&str_streaming_song, g_readbuffer, nbytes);
}
else {
samplesize = 4;
@@ -300,6 +527,7 @@ streamer_read_async (char *bytes, int size) {
codec_lock ();
bytesread = decoder->read_float32 (g_readbuffer, nbytes*2);
codec_unlock ();
+ apply_replay_gain_float32 (&str_streaming_song, g_readbuffer, nbytes*2);
nsamples = bytesread / (samplesize * nchannels) + codecleft;
for (i = 0; i < (nsamples - codecleft); i++) {
fbuffer[i*2+0] = ((float *)g_readbuffer)[i];
@@ -310,6 +538,7 @@ streamer_read_async (char *bytes, int size) {
codec_lock ();
bytesread = decoder->read_float32 ((char *)fbuffer, nbytes*2);
codec_unlock ();
+ apply_replay_gain_float32 (&str_streaming_song, (char *)fbuffer, nbytes*2);
nsamples = bytesread / (samplesize * nchannels) + codecleft;
}
}
@@ -355,8 +584,10 @@ streamer_read_async (char *bytes, int size) {
return initsize;
}
else {
+// trace ("EOF, buns=%d\n", bytes_until_next_song);
// that means EOF
- if (bytes_until_next_song == 0) {
+ if (bytes_until_next_song < 0) {
+// trace ("finished streaming song, queueing next\n");
bytes_until_next_song = streambuffer_fill;
pl_nextsong (0);
}
@@ -388,13 +619,16 @@ streamer_read (char *bytes, int size) {
}
streambuffer_fill -= sz;
playpos += (float)sz/p_get_rate ()/4.f;
- playlist_current.playtime += (float)sz/p_get_rate ()/4.f;
+ str_playing_song.playtime += (float)sz/p_get_rate ()/4.f;
if (playlist_current_ptr) {
- playlist_current_ptr->playtime = playlist_current.playtime;
+ playlist_current_ptr->playtime = str_playing_song.playtime;
}
- bytes_until_next_song -= sz;
- if (bytes_until_next_song < 0) {
- bytes_until_next_song = 0;
+ if (bytes_until_next_song > 0) {
+ bytes_until_next_song -= sz;
+ if (bytes_until_next_song < 0) {
+ bytes_until_next_song = 0;
+ }
+// trace ("buns: %d\n", bytes_until_next_song);
}
}
streamer_unlock ();
diff --git a/streamer.h b/streamer.h
index 7c0a987a..4f434223 100644
--- a/streamer.h
+++ b/streamer.h
@@ -18,6 +18,11 @@
#ifndef __STREAMER_H
#define __STREAMER_H
+#include "playlist.h"
+
+extern playItem_t str_playing_song;
+extern playItem_t str_streaming_song;
+
int
streamer_init (void);
@@ -58,4 +63,7 @@ streamer_get_playpos (void);
int
streamer_is_buffering (void);
+void
+streamer_song_removed_notify (playItem_t *it);
+
#endif // __STREAMER_H
diff --git a/volume.c b/volume.c
index 7b82623e..24e876ff 100644
--- a/volume.c
+++ b/volume.c
@@ -64,7 +64,10 @@ volume_get_amp (void) {
float
db_to_amp (float dB) {
- return pow (10, dB/20.f);
+// return pow (10, dB/20.f);
+ // thanks to he for this hack
+ const float ln10=2.3025850929940002f;
+ return exp(ln10*dB/20.f);
}
float