summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexey Yakovenko <wakeroid@gmail.com>2010-01-31 20:26:17 +0100
committerGravatar Alexey Yakovenko <wakeroid@gmail.com>2010-01-31 20:26:17 +0100
commit6e6dc88fc61f4a84269a592c76c2b6bd0417bf4a (patch)
tree8b5b1fe103a9a4e93a680493d9f1dd2fcb71ded2
parent9460844e2a1598dfe24a72dd2a283d02b57caddc (diff)
parent893655b27084db6399b82069a5dcb56d6e98fbfa (diff)
Merge branch 'master' into devel
Conflicts: main.c plugins/flac/flac.c plugins/mpgmad/mpgmad.c streamer.c
-rw-r--r--Makefile.am1
-rw-r--r--codec.c42
-rw-r--r--codec.h65
-rw-r--r--deadbeef.h3
-rw-r--r--dumb/dumb-kode54/include/dumb.h2
-rw-r--r--dumb/dumb-kode54/src/core/dumbfile.c2
-rw-r--r--dumb/dumb-kode54/src/helpers/stdfile.c4
-rw-r--r--main.c4
-rw-r--r--playlist.c170
-rw-r--r--playlist.h9
-rw-r--r--plugins.c3
-rw-r--r--plugins/alsa/alsa.c10
-rw-r--r--plugins/flac/flac.c77
-rw-r--r--plugins/mpgmad/mpgmad.c133
-rw-r--r--plugins/vfs_curl/vfs_curl.c308
-rw-r--r--streamer.c303
-rw-r--r--web/faq.txt44
17 files changed, 704 insertions, 476 deletions
diff --git a/Makefile.am b/Makefile.am
index c77e96e6..5b5fe6ea 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,7 +53,6 @@ deadbeef_SOURCES =\
plugins.c plugins.h moduleconf.h\
playlist.c playlist.h \
streamer.c streamer.h\
- codec.c codec.h\
messagepump.c messagepump.h\
conf.c conf.h\
playback.h\
diff --git a/codec.c b/codec.c
deleted file mode 100644
index 686c5bcf..00000000
--- a/codec.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- DeaDBeeF - ultimate music player for GNU/Linux systems with X11
- Copyright (C) 2009-2010 Alexey Yakovenko <waker@users.sourceforge.net>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-#include "codec.h"
-#include "threading.h"
-
-static uintptr_t mutex;
-
-void
-codec_init_locking (void) {
- mutex = mutex_create ();
-}
-
-void
-codec_free_locking (void) {
- mutex_free (mutex);
-}
-
-void
-codec_lock (void) {
- mutex_lock (mutex);
-}
-
-void
-codec_unlock (void) {
- mutex_unlock (mutex);
-}
-
diff --git a/codec.h b/codec.h
deleted file mode 100644
index 1b6ad2a3..00000000
--- a/codec.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- DeaDBeeF - ultimate music player for GNU/Linux systems with X11
- Copyright (C) 2009-2010 Alexey Yakovenko <waker@users.sourceforge.net>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-#ifndef __CODEC_H
-#define __CODEC_H
-
-#include <stdint.h>
-//#include "playlist.h"
-
-#if 0
-typedef struct {
- int bitsPerSample;
- int channels;
- int samplesPerSecond;
-// float position;
- float readposition;
-} fileinfo_t;
-
-struct playItem_s;
-
-typedef struct codec_s {
- int (*init) (struct playItem_s *it);
- void (*free) (void);
- // player is responsible for starting next song if -1 is returned
- int (*read) (char *bytes, int size);
- int (*seek) (float time);
- struct playItem_s * (*insert) (struct playItem_s *after, const char *fname); // after==NULL means "prepend to beginning"
- const char ** (*getexts) (void);
- int (*numvoices) (void);
- void (*mutevoice) (int voice, int mute);
- const char *id; // codec id used for playlist serialization
- const char *filetypes[20]; // NULL terminated array of const strings, representing supported file types (can be NULL)
- fileinfo_t info;
-} codec_t;
-#endif
-
-//codec_t *get_codec_for_file (const char *fname);
-
-void
-codec_init_locking (void);
-
-void
-codec_free_locking (void);
-
-void
-codec_lock (void);
-
-void
-codec_unlock (void);
-
-#endif // __CODEC_H
diff --git a/deadbeef.h b/deadbeef.h
index b1fb074c..585fe3f2 100644
--- a/deadbeef.h
+++ b/deadbeef.h
@@ -343,6 +343,7 @@ 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);
void (*pl_delete_all_meta) (DB_playItem_t *it);
+ void (*pl_replace_meta) (DB_playItem_t *it, const char *key, const char *value);
void (*pl_set_item_duration) (DB_playItem_t *it, float duration);
float (*pl_get_item_duration) (DB_playItem_t *it);
void (*pl_sort) (int iter, int id, const char *format, int ascending);
@@ -367,6 +368,8 @@ typedef struct {
int (*junk_read_ape) (DB_playItem_t *it, DB_FILE *fp);
int (*junk_get_leading_size) (DB_FILE *fp);
void (*junk_copy) (DB_playItem_t *from, DB_playItem_t *first, DB_playItem_t *last);
+ const char * (*junk_detect_charset) (const char *s);
+ void (*junk_recode) (const char *in, int inlen, char *out, int outlen, const char *cs);
// vfs
DB_FILE* (*fopen) (const char *fname);
void (*fclose) (DB_FILE *f);
diff --git a/dumb/dumb-kode54/include/dumb.h b/dumb/dumb-kode54/include/dumb.h
index d6e0745c..63718e1e 100644
--- a/dumb/dumb-kode54/include/dumb.h
+++ b/dumb/dumb-kode54/include/dumb.h
@@ -165,7 +165,7 @@ DUMBFILE_SYSTEM;
typedef struct DUMBFILE DUMBFILE;
-void register_dumbfile_system(const DUMBFILE_SYSTEM *dfs);
+void register_dumbfile_system(DUMBFILE_SYSTEM *dfs);
DUMBFILE *dumbfile_open(const char *filename);
DUMBFILE *dumbfile_open_ex(void *file, DUMBFILE_SYSTEM *dfs);
diff --git a/dumb/dumb-kode54/src/core/dumbfile.c b/dumb/dumb-kode54/src/core/dumbfile.c
index 4c5547a5..ae738bf7 100644
--- a/dumb/dumb-kode54/src/core/dumbfile.c
+++ b/dumb/dumb-kode54/src/core/dumbfile.c
@@ -27,7 +27,7 @@ static DUMBFILE_SYSTEM *the_dfs = NULL;
-void register_dumbfile_system(const DUMBFILE_SYSTEM *dfs)
+void register_dumbfile_system(DUMBFILE_SYSTEM *dfs)
{
ASSERT(dfs);
ASSERT(dfs->open);
diff --git a/dumb/dumb-kode54/src/helpers/stdfile.c b/dumb/dumb-kode54/src/helpers/stdfile.c
index c4cd5b3a..aa398f50 100644
--- a/dumb/dumb-kode54/src/helpers/stdfile.c
+++ b/dumb/dumb-kode54/src/helpers/stdfile.c
@@ -58,7 +58,7 @@ static void dumb_stdfile_close(void *f)
-static const DUMBFILE_SYSTEM stdfile_dfs = {
+static DUMBFILE_SYSTEM stdfile_dfs = {
&dumb_stdfile_open,
&dumb_stdfile_skip,
&dumb_stdfile_getc,
@@ -75,7 +75,7 @@ void dumb_register_stdfiles(void)
-static const DUMBFILE_SYSTEM stdfile_dfs_leave_open = {
+static DUMBFILE_SYSTEM stdfile_dfs_leave_open = {
NULL,
&dumb_stdfile_skip,
&dumb_stdfile_getc,
diff --git a/main.c b/main.c
index 7773dcf4..d555a291 100644
--- a/main.c
+++ b/main.c
@@ -45,7 +45,6 @@
#include "unistd.h"
#include "threading.h"
#include "messagepump.h"
-#include "codec.h"
#include "streamer.h"
#include "conf.h"
#include "volume.h"
@@ -603,7 +602,6 @@ main (int argc, char *argv[]) {
pl_load (defpl);
}
plug_trigger_event_playlistchanged ();
-
// this is old code left for backwards compatibility
{
char sessfile[1024]; // $HOME/.config/deadbeef/session
@@ -612,7 +610,6 @@ main (int argc, char *argv[]) {
}
}
- codec_init_locking ();
streamer_init ();
// this runs in main thread (blocks right here)
@@ -642,7 +639,6 @@ main (int argc, char *argv[]) {
plug_unload_all ();
// at this point we can simply do exit(0), but let's clean up for debugging
- codec_free_locking ();
pl_free ();
conf_free ();
messagepump_free ();
diff --git a/playlist.c b/playlist.c
index eb2dccf6..6ac561bc 100644
--- a/playlist.c
+++ b/playlist.c
@@ -31,7 +31,6 @@
#endif
#include <limits.h>
#include "playlist.h"
-#include "codec.h"
#include "streamer.h"
#include "messagepump.h"
#include "playback.h"
@@ -598,90 +597,111 @@ pl_insert_pls (playItem_t *after, const char *fname, int *pabort, int (*cb)(play
char title[1024] = "";
char length[20] = "";
while (p < end) {
+ p = pl_str_skipspaces (p, end);
if (p >= end) {
break;
}
if (end-p < 6) {
break;
}
- if (strncasecmp (p, "file", 4)) {
- break;
- }
- p += 4;
- while (p < end && *p != '=') {
+ const uint8_t *e;
+ int n;
+ if (!strncasecmp (p, "file", 4)) {
+ if (url[0]) {
+ // add track
+ playItem_t *it = pl_insert_file (after, url, pabort, cb, user_data);
+ if (it) {
+ after = it;
+ pl_set_item_duration (it, atoi (length));
+ if (title[0]) {
+ pl_delete_all_meta (it);
+ pl_add_meta (it, "title", title);
+ }
+ }
+ if (pabort && *pabort) {
+ return after;
+ }
+ url[0] = 0;
+ title[0] = 0;
+ length[0] = 0;
+ }
+ p += 4;
+ while (p < end && *p != '=') {
+ p++;
+ }
p++;
+ if (p >= end) {
+ break;
+ }
+ e = p;
+ while (e < end && *e >= 0x20) {
+ e++;
+ }
+ n = e-p;
+ n = min (n, sizeof (url)-1);
+ memcpy (url, p, n);
+ url[n] = 0;
+ trace ("url: %s\n", url);
+ p = ++e;
}
- p++;
- if (p >= end) {
- break;
- }
- const uint8_t *e = p;
- while (e < end && *e >= 0x20) {
- e++;
- }
- int n = e-p;
- n = min (n, sizeof (url)-1);
- memcpy (url, p, n);
- url[n] = 0;
- trace ("url: %s\n", url);
- p = ++e;
- p = pl_str_skipspaces (p, end);
- if (strncasecmp (p, "title", 5)) {
- break;
- }
- p += 5;
- while (p < end && *p != '=') {
+ else if (!strncasecmp (p, "title", 5)) {
+ p += 5;
+ while (p < end && *p != '=') {
+ p++;
+ }
p++;
- }
- p++;
- if (p >= end) {
- break;
- }
- e = p;
- while (e < end && *e >= 0x20) {
- e++;
- }
- n = e-p;
- n = min (n, sizeof (title)-1);
- memcpy (title, p, n);
- title[n] = 0;
- trace ("title: %s\n", title);
- p = ++e;
- p = pl_str_skipspaces (p, end);
- if (strncasecmp (p, "length", 6)) {
- break;
- }
- p += 6;
- // skip =
- while (p < end && *p != '=') {
+ if (p >= end) {
+ break;
+ }
+ e = p;
+ while (e < end && *e >= 0x20) {
+ e++;
+ }
+ n = e-p;
+ n = min (n, sizeof (title)-1);
+ memcpy (title, p, n);
+ title[n] = 0;
+ trace ("title: %s\n", title);
+ p = ++e;
+ }
+ else if (!strncasecmp (p, "length", 6)) {
+ p += 6;
+ // skip =
+ while (p < end && *p != '=') {
+ p++;
+ }
p++;
+ if (p >= end) {
+ break;
+ }
+ e = p;
+ while (e < end && *e >= 0x20) {
+ e++;
+ }
+ n = e-p;
+ n = min (n, sizeof (length)-1);
+ memcpy (length, p, n);
+ break;
}
- p++;
- if (p >= end) {
+ else {
+ trace ("invalid entry in pls file: %s\n", p);
break;
}
- e = p;
- while (e < end && *e >= 0x20) {
+ while (e < end && *e < 0x20) {
e++;
}
- n = e-p;
- n = min (n, sizeof (length)-1);
- memcpy (length, p, n);
- // add track
+ p = e;
+ }
+ if (url[0]) {
playItem_t *it = pl_insert_file (after, url, pabort, cb, user_data);
if (it) {
after = it;
pl_set_item_duration (it, atoi (length));
- pl_delete_all_meta (it);
- pl_add_meta (it, "title", title);
- }
- if (pabort && *pabort) {
- return after;
- }
- while (e < end && *e < 0x20) {
- e++;
+ if (title[0]) {
+ pl_delete_all_meta (it);
+ pl_add_meta (it, "title", title);
+ }
}
- p = e;
}
return after;
}
@@ -1091,6 +1111,26 @@ pl_add_meta (playItem_t *it, const char *key, const char *value) {
}
void
+pl_replace_meta (playItem_t *it, const char *key, const char *value) {
+ // check if it's already set
+ metaInfo_t *m = it->meta;
+ while (m) {
+ if (!strcasecmp (key, m->key)) {
+ break;;
+ }
+ m = m->next;
+ }
+ if (m) {
+ free (m->value);
+ m->value = strdup (value);
+ return;
+ }
+ else {
+ pl_add_meta (it, key, value);
+ }
+}
+
+void
pl_format_item_display_name (playItem_t *it, char *str, int len) {
const char *artist = pl_find_meta (it, "artist");
const char *title = pl_find_meta (it, "title");
diff --git a/playlist.h b/playlist.h
index 0d37e1b4..c168c4aa 100644
--- a/playlist.h
+++ b/playlist.h
@@ -146,13 +146,16 @@ pl_insert_cue (playItem_t *after, playItem_t *origin, int numsamples, int sample
void
pl_add_meta (playItem_t *it, const char *key, const char *value);
-void
-pl_format_item_display_name (playItem_t *it, char *str, int len);
-
const char *
pl_find_meta (playItem_t *it, const char *key);
void
+pl_replace_meta (playItem_t *it, const char *key, const char *value);
+
+void
+pl_format_item_display_name (playItem_t *it, char *str, int len);
+
+void
pl_delete_all_meta (playItem_t *it);
// returns index of 1st deleted item
diff --git a/plugins.c b/plugins.c
index 81aaf3d0..ab710e43 100644
--- a/plugins.c
+++ b/plugins.c
@@ -143,6 +143,7 @@ static DB_functions_t deadbeef_api = {
// metainfo
.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,
+ .pl_replace_meta = (void (*) (DB_playItem_t *, const char *, const char *))pl_replace_meta,
.pl_delete_all_meta = (void (*) (DB_playItem_t *it))pl_delete_all_meta,
// cuesheet support
.pl_insert_cue_from_buffer = (DB_playItem_t *(*) (DB_playItem_t *after, DB_playItem_t *origin, const uint8_t *buffer, int buffersize, int numsamples, int samplerate))pl_insert_cue_from_buffer,
@@ -164,6 +165,8 @@ static DB_functions_t deadbeef_api = {
.junk_read_ape = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_read_ape,
.junk_get_leading_size = junk_get_leading_size,
.junk_copy = (void (*)(DB_playItem_t *from, DB_playItem_t *first, DB_playItem_t *last))junk_copy,
+ .junk_detect_charset = junk_detect_charset,
+ .junk_recode = junk_recode,
// vfs
.fopen = vfs_fopen,
.fclose = vfs_fclose,
diff --git a/plugins/alsa/alsa.c b/plugins/alsa/alsa.c
index 2017907c..ef1a4782 100644
--- a/plugins/alsa/alsa.c
+++ b/plugins/alsa/alsa.c
@@ -182,10 +182,12 @@ palsa_set_hw_params (int samplerate) {
req_period_size = deadbeef->conf_get_int ("alsa.period", 512);
trace ("trying buffer size: %d frames\n", req_buffer_size);
trace ("trying period size: %d frames\n", req_period_size);
- snd_pcm_hw_params_set_buffer_size_near (audio, hw_params, &buffer_size);
- snd_pcm_hw_params_set_period_size_near (audio, hw_params, &period_size, NULL);
- trace ("alsa buffer size: %d frames\n", buffer_size);
- trace ("alsa period size: %d frames\n", period_size);
+ snd_pcm_hw_params_set_buffer_size_near (audio, hw_params, &req_buffer_size);
+ snd_pcm_hw_params_set_period_size_near (audio, hw_params, &req_period_size, NULL);
+ trace ("alsa buffer size: %d frames\n", req_buffer_size);
+ trace ("alsa period size: %d frames\n", req_period_size);
+ buffer_size = req_buffer_size;
+ period_size = req_period_size;
if ((err = snd_pcm_hw_params (audio, hw_params)) < 0) {
trace ("cannot set parameters (%s)\n",
diff --git a/plugins/flac/flac.c b/plugins/flac/flac.c
index dbb355d3..173df64c 100644
--- a/plugins/flac/flac.c
+++ b/plugins/flac/flac.c
@@ -112,20 +112,21 @@ cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *fra
if (bitrate > 0) {
deadbeef->streamer_set_bitrate (bitrate/1000);
}
- int readbytes = frame->header.blocksize * _info->channels * _info->bps / 8;
int bufsize = BUFFERSIZE - info->remaining;
int bufsamples = bufsize / (_info->channels * _info->bps / 8);
int nsamples = min (bufsamples, frame->header.blocksize);
char *bufptr = &info->buffer[info->remaining];
float mul = 1.f/ ((1 << (_info->bps-1))-1);
+ int channels = _info->channels;
+ if (channels > 2) {
+ channels = 2;
+ }
+ int readbytes = frame->header.blocksize * channels * _info->bps / 8;
+
for (int i = 0; i < nsamples; i++) {
- int32_t sample = inputbuffer[0][i];
- *((float*)bufptr) = sample * mul;
- bufptr += sizeof (float);
- info->remaining += sizeof (float);
- if (_info->channels > 1) {
- int32_t sample = inputbuffer[1][i];
+ for (int c = 0; c < channels; c++) {
+ int32_t sample = inputbuffer[c][i];
*((float*)bufptr) = sample * mul;
bufptr += sizeof (float);
info->remaining += sizeof (float);
@@ -311,25 +312,39 @@ cflac_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) {
return 0;
}
}
+ int n_output_channels = _info->channels;
+ if (n_output_channels > 2) {
+ n_output_channels = 2;
+ }
int initsize = size;
do {
if (info->remaining) {
- int s = size * 2;
- int sz = min (info->remaining, s);
+ int n_input_frames = info->remaining / sizeof (float) / n_output_channels;
+ int n_output_frames = size / n_output_channels / sizeof (int16_t);
+ int n = min (n_input_frames, n_output_frames);
+
+ trace ("flac: [1] if=%d, of=%d, n=%d, rem=%d, size=%d\n", n_input_frames, n_output_frames, n, remaining, size);
// convert from float to int16
float *in = (float *)info->buffer;
- for (int i = 0; i < sz/4; i++) {
+ for (int i = 0; i < n; i++) {
*((int16_t *)bytes) = (int16_t)((*in) * 0x7fff);
- size -= 2;
- bytes += 2;
- in++;
+ size -= sizeof (int16_t);
+ bytes += sizeof (int16_t);
+ if (n_output_channels == 2) {
+ *((int16_t *)bytes) = (int16_t)((*(in+1)) * 0x7fff);
+ size -= sizeof (int16_t);
+ bytes += sizeof (int16_t);
+ }
+ in += n_output_channels;
}
+ int sz = n * sizeof (float) * n_output_channels;
if (sz < info->remaining) {
- memmove (info->buffer, &info->buffer[sz], info->remaining-sz);
+ memmove (info->buffer, &info->buffer[sz], info->remaining - sz);
}
info->remaining -= sz;
- info->currentsample += sz / (4 * _info->channels);
- _info->readpos += (float)sz / (_info->channels * _info->samplerate * sizeof (float));
+ info->currentsample += n;
+ _info->readpos += (float)n / _info->samplerate;
+ trace ("flac: [2] if=%d, of=%d, n=%d, rem=%d, size=%d\n", n_input_frames, n_output_frames, n, info->remaining, size);
}
if (!size) {
break;
@@ -361,19 +376,36 @@ cflac_read_float32 (DB_fileinfo_t *_info, char *bytes, int size) {
return 0;
}
}
+ int n_output_channels = _info->channels;
+ if (n_output_channels > 2) {
+ n_output_channels = 2;
+ }
int initsize = size;
do {
if (info->remaining) {
- int sz = min (info->remaining, size);
- memcpy (bytes, info->buffer, sz);
- size -= sz;
- bytes += sz;
+ int n_input_frames = info->remaining / sizeof (float) / n_output_channels;
+ int n_output_frames = size / n_output_channels / sizeof (float);
+ int n = min (n_input_frames, n_output_frames);
+
+ float *in = (float *)info->buffer;
+ for (int i = 0; i < n; i++) {
+ *((float *)bytes) = *in;
+ size -= sizeof (float);
+ bytes += sizeof (float);
+ if (n_output_channels == 2) {
+ *((float *)bytes) = *(in+1);
+ size -= sizeof (float);
+ bytes += sizeof (float);
+ }
+ in += n_output_channels;
+ }
+ int sz = n * sizeof (float) * n_output_channels;
if (sz < info->remaining) {
memmove (info->buffer, &info->buffer[sz], info->remaining-sz);
}
info->remaining -= sz;
- info->currentsample += sz / (4 * _info->channels);
- _info->readpos += (float)sz / (_info->channels * _info->samplerate * sizeof (int32_t));
+ info->currentsample += n;
+ _info->readpos += (float)n / _info->samplerate;
}
if (!size) {
break;
@@ -451,6 +483,7 @@ cflac_init_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__Str
DB_playItem_t *it = info->it;
//it->tracknum = 0;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
+ trace ("flac: samplerate=%d, channels=%d\n", metadata->data.stream_info.sample_rate, metadata->data.stream_info.channels);
_info->samplerate = metadata->data.stream_info.sample_rate;
_info->channels = metadata->data.stream_info.channels;
info->totalsamples = metadata->data.stream_info.total_samples;
diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c
index d2355dc5..9a925304 100644
--- a/plugins/mpgmad/mpgmad.c
+++ b/plugins/mpgmad/mpgmad.c
@@ -83,6 +83,11 @@ typedef struct {
int endsample;
int startdelay;
int enddelay;
+ int avg_packetlength;
+ int avg_samplerate;
+ int avg_samples_per_frame;
+ int nframes;
+ int last_comment_update;
} buffer_t;
typedef struct {
@@ -162,6 +167,8 @@ extract_f32 (unsigned char *buf) {
static int
cmp3_scan_stream (buffer_t *buffer, int sample) {
trace ("cmp3_scan_stream %d\n", sample);
+ int initpos = deadbeef->ftell (buffer->file);
+ trace ("filepos: %d\n", initpos);
// if (sample == 0) {
// sample = -1;
// }
@@ -173,13 +180,19 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
buffer->currentsample = 0;
buffer->skipsamples = 0;
int fsize = 0;
- int avg_packetlength = 0;
- int avg_samplerate = 0;
- int avg_samples_per_frame = 0;
+ int avg_bitrate = 0;
+ int valid_frames = 0;
if (sample <= 0) {
buffer->totalsamples = 0;
- fsize = deadbeef->fgetlength (buffer->file);
+ fsize = deadbeef->fgetlength (buffer->file) - initpos;
+ }
+ if (sample == 0 && buffer->avg_packetlength == 0) {
+ buffer->avg_packetlength = 0;
+ buffer->avg_samplerate = 0;
+ buffer->avg_samples_per_frame = 0;
+ buffer->nframes = 0;
+ buffer->startoffset = initpos;
}
for (;;) {
@@ -190,7 +203,7 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
break; // eof
}
if (sync != 0xff) {
- trace ("[1]frame %d didn't seek to frame end\n", nframe);
+// trace ("[1]frame %d didn't seek to frame end\n", nframe);
continue; // not an mpeg frame
}
else {
@@ -199,7 +212,7 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
break; // eof
}
if ((sync >> 5) != 7) {
- trace ("[2]frame %d didn't seek to frame end\n", nframe);
+// trace ("[2]frame %d didn't seek to frame end\n", nframe);
continue;
}
}
@@ -286,11 +299,11 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
// check if channel/bitrate combination is valid for layer2
if (layer == 2) {
if ((bitrate <= 56 || bitrate == 80) && nchannels != 1) {
- trace ("[1]frame %d channel/bitrate combination is bad\n", nframe);
+ trace ("mpgmad: bad frame %d: layer %d, channels %d, bitrate %d\n", nframe, layer, nchannels, bitrate);
continue; // bad frame
}
if (bitrate >= 224 && nchannels == 1) {
- trace ("[2]frame %d channel/bitrate combination is bad\n", nframe);
+ trace ("mpgmad: bad frame %d: layer %d, channels %d, bitrate %d\n", nframe, layer, nchannels, bitrate);
continue; // bad frame
}
}
@@ -326,6 +339,10 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
continue;
}
+ valid_frames++;
+ if (nframe < 1000) {
+ trace ("frame %d bitrate %d\n", nframe, bitrate);
+ }
if (sample != 0 || nframe == 0)
{
buffer->version = ver;
@@ -342,7 +359,7 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
if (sample <= 0 && !got_xing_header)
{
size_t framepos = deadbeef->ftell (buffer->file);
- trace ("trying to read xing header\n");
+// trace ("trying to read xing header at pos %d\n", framepos);
if (ver == 1) {
deadbeef->fseek (buffer->file, 32, SEEK_CUR);
}
@@ -361,7 +378,7 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
buffer->startoffset = startoffset;
}
- trace ("xing magic: %c%c%c%c\n", magic[0], magic[1], magic[2], magic[3]);
+// trace ("xing magic: %c%c%c%c\n", magic[0], magic[1], magic[2], magic[3]);
if (!strncmp (xing, magic, 4) || !strncmp (info, magic, 4)) {
trace ("xing/info frame found\n");
@@ -396,7 +413,7 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) {
return -1; // EOF
}
- trace ("tell=%x, %c%c%c%c\n", deadbeef->ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]);
+// trace ("tell=%x, %c%c%c%c\n", deadbeef->ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]);
if (!memcmp (buf, "LAME", 4)) {
trace ("lame header found\n");
deadbeef->fseek (buffer->file, 6, SEEK_CUR);
@@ -444,29 +461,36 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
}
if (sample == 0) {
// xing header failed, calculate based on file size
- trace ("xing header failed\n");
+// trace ("xing header failed\n");
buffer->samplerate = samplerate;
if (buffer->file->vfs->streaming) {
// only suitable for cbr files, used if streaming
int sz = deadbeef->fgetlength (buffer->file) - buffer->startoffset - buffer->endoffset;
if (sz < 0) {
+ // unable to determine duration
buffer->duration = -1;
buffer->totalsamples = -1;
if (sample == 0) {
- deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ deadbeef->fseek (buffer->file, framepos/*+packetlength-4*/, SEEK_SET);
}
return 0;
}
- int nframes = sz / packetlength;
- buffer->duration = nframes * samples_per_frame / samplerate;
- buffer->totalsamples = nframes * samples_per_frame;
+ buffer->nframes = sz / packetlength;
+ buffer->avg_packetlength = packetlength;
+ buffer->avg_samplerate = samplerate;
+ buffer->avg_samples_per_frame = samples_per_frame;
+ buffer->duration = buffer->nframes * samples_per_frame / samplerate;
+ buffer->totalsamples = buffer->nframes * samples_per_frame;
// trace ("bitrate=%d, layer=%d, packetlength=%d, fsize=%d, nframes=%d, samples_per_frame=%d, samplerate=%d, duration=%f, totalsamples=%d\n", bitrate, layer, packetlength, sz, nframes, samples_per_frame, samplerate, buffer->duration, buffer->totalsamples);
if (sample == 0) {
- deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
+ deadbeef->fseek (buffer->file, framepos/*+packetlength-4*/, SEEK_SET);
return 0;
}
}
+ else {
+ deadbeef->fseek (buffer->file, framepos, SEEK_SET);
+ }
}
else {
deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET);
@@ -479,19 +503,20 @@ cmp3_scan_stream (buffer_t *buffer, int sample) {
return -1;
}
// calculating apx duration based on 1st 100 frames
- avg_packetlength += packetlength;
- avg_samplerate += samplerate;
- avg_samples_per_frame += samples_per_frame;
+ buffer->avg_packetlength += packetlength;
+ buffer->avg_samplerate += samplerate;
+ buffer->avg_samples_per_frame += samples_per_frame;
+ avg_bitrate += bitrate;
if (nframe >= 100) {
- avg_packetlength /= nframe;
- avg_samplerate /= nframe;
- avg_samples_per_frame /= nframe;
-
- trace ("avg_packetlength=%d, avg_samplerate=%d, avg_samples_per_frame=%d\n", avg_packetlength, avg_samplerate, avg_samples_per_frame);
-
- int nframes = fsize / avg_packetlength;
- buffer->duration = nframes * avg_samples_per_frame / avg_samplerate;
- buffer->totalsamples = nframes * avg_samples_per_frame;
+ buffer->avg_packetlength /= valid_frames;
+ buffer->avg_samplerate /= valid_frames;
+ buffer->avg_samples_per_frame /= valid_frames;
+ avg_bitrate /= valid_frames;
+ trace ("valid_frames=%d, avg_bitrate=%d, avg_packetlength=%d, avg_samplerate=%d, avg_samples_per_frame=%d\n", valid_frames, avg_bitrate, buffer->avg_packetlength, buffer->avg_samplerate, buffer->avg_samples_per_frame);
+
+ buffer->nframes = fsize / buffer->avg_packetlength;
+ buffer->duration = buffer->nframes * buffer->avg_samples_per_frame / buffer->avg_samplerate;
+ buffer->totalsamples = buffer->nframes * buffer->avg_samples_per_frame;
return 0;
}
}
@@ -536,6 +561,7 @@ cmp3_init (DB_playItem_t *it) {
if (!info->buffer.file->vfs->streaming) {
int skip = deadbeef->junk_get_leading_size (info->buffer.file);
if (skip > 0) {
+ trace ("mpgmad: skipping %d bytes of junk\n", skip);
deadbeef->fseek (info->buffer.file, skip, SEEK_SET);
}
cmp3_scan_stream (&info->buffer, -1); // scan entire stream, calc duration
@@ -813,6 +839,30 @@ cmp3_stream_frame (mpgmad_info_t *info) {
deadbeef->streamer_set_bitrate (info->frame.header.bitrate/1000);
break;
}
+
+ if (!eof) {
+ if (info->buffer.file->vfs->streaming && info->buffer.currentsample - info->buffer.last_comment_update > 5 * info->info.samplerate) {
+ int idx = deadbeef->pl_get_idx_of (info->buffer.it);
+ if (idx >= 0) {
+ info->buffer.last_comment_update = info->buffer.currentsample;
+ const char *vfs_tit = deadbeef->fget_content_name (info->buffer.file);
+ if (vfs_tit) {
+ const char *cs = deadbeef->junk_detect_charset (vfs_tit);
+ if (cs) {
+ char out[1024];
+ deadbeef->junk_recode (vfs_tit, strlen (vfs_tit), out, sizeof (out), cs);
+ deadbeef->pl_replace_meta (info->buffer.it, "title", out);
+ deadbeef->sendmessage (M_TRACKCHANGED, 0, idx, 0);
+ }
+ else {
+ deadbeef->pl_replace_meta (info->buffer.it, "title", vfs_tit);
+ deadbeef->sendmessage (M_TRACKCHANGED, 0, idx, 0);
+ }
+ }
+ }
+ }
+ }
+
return eof;
}
@@ -896,15 +946,28 @@ cmp3_seek_sample (DB_fileinfo_t *_info, int sample) {
}
if (info->buffer.file->vfs->streaming) {
- if (info->buffer.totalsamples > 0) {
- // approximation
- int64_t l = deadbeef->fgetlength (info->buffer.file);
- l = l * sample / info->buffer.totalsamples;
- int r = deadbeef->fseek (info->buffer.file, l, SEEK_SET);
+ if (info->buffer.totalsamples > 0 && info->buffer.avg_samples_per_frame && info->buffer.avg_packetlength) { // that means seekable remote stream, like podcast
+ trace ("seeking is possible!\n");
+ // get length excluding id3v2
+ int64_t l = deadbeef->fgetlength (info->buffer.file) - info->buffer.startoffset;
+
+ int r;
+
+ // seek to beginning of the frame
+ int frm = sample / info->buffer.avg_samples_per_frame;
+ r = deadbeef->fseek (info->buffer.file, frm * info->buffer.avg_packetlength, SEEK_SET);
+
+// l = l * sample / buffer.totalsamples;
+// r = deadbeef->fseek (buffer.file, l, SEEK_SET);
+
if (!r) {
+ trace ("seek successful!\n");
+ info->buffer.skipsamples = sample - frm * info->buffer.avg_samples_per_frame;
+
info->buffer.currentsample = sample;
_info->readpos = (float)(info->buffer.currentsample - info->buffer.startsample) / info->buffer.samplerate;
+ // reset mad
mad_synth_finish (&info->synth);
mad_frame_finish (&info->frame);
mad_stream_finish (&info->stream);
@@ -916,8 +979,10 @@ cmp3_seek_sample (DB_fileinfo_t *_info, int sample) {
return 0;
}
+ trace ("seek failed!\n");
return -1;
}
+ trace ("seek is impossible (avg_samples_per_frame=%d, avg_packetlength=%d)!\n", buffer.avg_samples_per_frame, buffer.avg_packetlength);
return 0;
}
diff --git a/plugins/vfs_curl/vfs_curl.c b/plugins/vfs_curl/vfs_curl.c
index 127a5a4e..972a5da9 100644
--- a/plugins/vfs_curl/vfs_curl.c
+++ b/plugins/vfs_curl/vfs_curl.c
@@ -24,8 +24,8 @@
#include <curl/curlver.h>
#include "../../deadbeef.h"
-//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
-#define trace(fmt,...)
+#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))
@@ -35,6 +35,8 @@ static DB_functions_t *deadbeef;
#define BUFFER_SIZE (0x10000)
#define BUFFER_MASK 0xffff
+#define TIMEOUT 10 // in seconds
+
#define STATUS_INITIAL 0
#define STATUS_STARTING 1
#define STATUS_READING 2
@@ -56,9 +58,11 @@ typedef struct {
char *content_type;
char *content_name;
char *content_genre;
+ CURL *curl;
+ struct timeval last_read_time;
uint8_t status;
-// int icy_metaint;
-// int wait_meta;
+ int icy_metaint;
+ int wait_meta;
// flags (bitfields to save some space)
unsigned seektoend : 1; // indicates that next tell must return length
unsigned gotheader : 1; // tells that all headers (including ICY) were processed (to start reading body)
@@ -77,10 +81,96 @@ static size_t
http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream);
static size_t
+http_curl_write_wrapper (HTTP_FILE *fp, void *ptr, size_t size) {
+ size_t avail = size;
+ while (avail > 0) {
+ if (vfs_curl_abort) {
+ break;
+ }
+ deadbeef->mutex_lock (fp->mutex);
+ if (fp->status == STATUS_SEEK) {
+ trace ("vfs_curl seek request, aborting current request\n");
+ deadbeef->mutex_unlock (fp->mutex);
+ return 0;
+ }
+ if (fp->status == STATUS_ABORTED) {
+ trace ("vfs_curl STATUS_ABORTED in the middle of packet\n");
+ deadbeef->mutex_unlock (fp->mutex);
+ break;
+ }
+ int sz = BUFFER_SIZE/2 - fp->remaining; // number of bytes free in buffer
+ // don't allow to fill more than half -- used for seeking backwards
+
+ if (sz > 5000) { // wait until there are at least 5k bytes free
+ int cp = min (avail, sz);
+ int writepos = (fp->pos + fp->remaining) & BUFFER_MASK;
+ // copy 1st portion (before end of buffer
+ int part1 = BUFFER_SIZE - writepos;
+ // may not be more than total
+ part1 = min (part1, cp);
+ memcpy (fp->buffer+writepos, ptr, part1);
+ ptr += part1;
+ avail -= part1;
+ fp->remaining += part1;
+ cp -= part1;
+ if (cp > 0) {
+ memcpy (fp->buffer, ptr, cp);
+ ptr += cp;
+ avail -= cp;
+ fp->remaining += cp;
+ }
+ }
+ deadbeef->mutex_unlock (fp->mutex);
+ usleep (3000);
+ }
+ return size - avail;
+}
+
+int
+http_parse_shoutcast_meta (HTTP_FILE *fp, const char *meta, int size) {
+ trace ("reading %d bytes of metadata\n", size);
+ trace ("%s\n", meta);
+ const char *e = meta + size;
+ const char strtitle[] = "StreamTitle='";
+ char title[256] = "";
+ while (meta < e) {
+ if (!memcmp (meta, strtitle, sizeof (strtitle)-1)) {
+ trace ("extracting streamtitle\n");
+ meta += sizeof (strtitle)-1;
+ const char *substr_end = meta;
+ while (substr_end < e-1 && (*substr_end != '\'' || *(substr_end+1) != ';')) {
+ substr_end++;
+ }
+ if (substr_end >= e) {
+ return -1; // end of string not found
+ }
+ int s = substr_end - meta;
+ s = min (sizeof (title)-1, s);
+ memcpy (title, meta, s);
+ title[s] = 0;
+ trace ("got stream title: %s\n", title);
+ if (fp->content_name) {
+ free (fp->content_name);
+ }
+ fp->content_name = strdup (title);
+ return 0;
+ }
+ while (meta < e && *meta != ';') {
+ meta++;
+ }
+ if (meta < e) {
+ meta++;
+ }
+ }
+ return -1;
+}
+
+static size_t
http_curl_write (void *ptr, size_t size, size_t nmemb, void *stream) {
-// trace ("http_curl_write %d bytes\n", size * nmemb);
int avail = size * nmemb;
HTTP_FILE *fp = (HTTP_FILE *)stream;
+ trace ("http_curl_write %d bytes, wait_meta=%d\n", size * nmemb, fp->wait_meta);
+ gettimeofday (&fp->last_read_time, NULL);
if (fp->status == STATUS_ABORTED) {
trace ("vfs_curl STATUS_ABORTED at start of packet\n");
return 0;
@@ -100,29 +190,25 @@ http_curl_write (void *ptr, size_t size, size_t nmemb, void *stream) {
fp->gotheader = 1;
}
else {
- trace ("parsing icy headers:\n%s\n", ptr);
+// trace ("parsing icy headers:\n%s\n", ptr);
fp->nheaderpackets++;
- http_content_header_handler (ptr, size, nmemb, stream);
- if (fp->gotheader) {
- fp->gotheader = 0; // don't reset icy header
- }
- uint8_t *p = ptr;
- int i;
- for (i = 0; i < avail-3; i++) {
- const char end[4] = { 0x0d, 0x0a, 0x0d, 0x0a };
- if (!memcmp (p, end, 4)) {
- trace ("icy headers end\n");
- fp->gotheader = 1;
- break;
+ avail = http_content_header_handler (ptr, size, nmemb, stream);
+ if (avail == size * nmemb) {
+ if (fp->gotheader) {
+ fp->gotheader = 0; // don't reset icy header
}
- p++;
}
- avail = 0;
+ else {
+ fp->gotheader = 1;
+ }
}
}
else {
fp->gotheader = 1;
}
+ if (!avail) {
+ return nmemb*size;
+ }
}
deadbeef->mutex_lock (fp->mutex);
@@ -130,69 +216,43 @@ http_curl_write (void *ptr, size_t size, size_t nmemb, void *stream) {
fp->status = STATUS_READING;
}
deadbeef->mutex_unlock (fp->mutex);
- while (avail > 0) {
- if (vfs_curl_abort) {
- break;
- }
- deadbeef->mutex_lock (fp->mutex);
- if (fp->status == STATUS_SEEK) {
- trace ("vfs_curl seek request, aborting current request\n");
- deadbeef->mutex_unlock (fp->mutex);
- return 0;
- }
- if (fp->status == STATUS_ABORTED) {
- trace ("vfs_curl STATUS_ABORTED in the middle of packet\n");
- deadbeef->mutex_unlock (fp->mutex);
- break;
- }
-#if 0
- if (fp->wait_meta == 0) {
- char sz;
- memcpy (&sz, ptr, 1);
- printf ("reading %d bytes of metadata, seekpos:%d!\n", (int)sz*4, fp->pos);
- ptr += 16 * sz;
- avail -= 16 * sz + 1;
- printf ("avail=%d!\n", avail);
- fp->wait_meta = fp->icy_metaint;
- }
-#endif
- int sz = BUFFER_SIZE/2 - fp->remaining; // number of bytes free in buffer
- // don't allow to fill more than half -- used for seeking backwards
- if (sz > 5000) { // wait until there are at least 5k bytes free
- int cp = min (avail, sz);
-#if 0
- if (fp->wait_meta - cp <= 0) {
- printf ("cp=%d->%d\n", cp, fp->wait_meta);
- cp = fp->wait_meta;
+ if (fp->icy_metaint > 0) {
+ while (fp->wait_meta < avail) {
+ trace ("http_curl_write_wrapper [1] %d\n", fp->wait_meta);
+ size_t res1 = http_curl_write_wrapper (fp, ptr, fp->wait_meta);
+ if (res1 != fp->wait_meta) {
+ return 0;
}
- fp->wait_meta -= cp;
-#endif
-
- int writepos = (fp->pos + fp->remaining) & BUFFER_MASK;
- // copy 1st portion (before end of buffer
- int part1 = BUFFER_SIZE - writepos;
- // may not be more than total
- part1 = min (part1, cp);
- //trace ("part1=%d\n", part1);
-// trace ("writepos=%d, remaining=%d, avail=%d, free=%d, writing=%d, part1=%d, part2=%d\n", writepos, fp->remaining, avail, sz, cp, part1, cp-part1);
- memcpy (fp->buffer+writepos, ptr, part1);
- ptr += part1;
- avail -= part1;
- fp->remaining += part1;
- cp -= part1;
- if (cp > 0) {
- memcpy (fp->buffer, ptr, cp);
- ptr += cp;
- avail -= cp;
- fp->remaining += cp;
+ avail -= res1;
+ ptr += res1;
+ uint32_t sz = (uint32_t)(*((uint8_t *)ptr)) * 16;
+ ptr ++;
+ if (sz > 0) {
+ if (http_parse_shoutcast_meta (fp, ptr, sz) < 0) {
+ trace ("vfs_curl: got invalid icy metadata block\n");
+ fp->remaining = 0;
+ fp->status = STATUS_SEEK;
+ return 0;
+ }
}
+ avail -= sz + 1;
+ fp->wait_meta = fp->icy_metaint;
+ trace ("avail: %d\n", avail);
}
- deadbeef->mutex_unlock (fp->mutex);
- usleep (3000);
}
-// trace ("returning %d\n", nmemb * size - avail);
+ if (avail < 0) {
+ trace ("vfs_curl: something bad happened in metadata parser. can't continue streaming.\n");
+ return 0;
+ }
+
+ if (avail) {
+ trace ("http_curl_write_wrapper [2] %d\n", avail);
+ size_t res = http_curl_write_wrapper (fp, ptr, avail);
+ avail -= res;
+ fp->wait_meta -= res;
+ }
return nmemb * size - avail;
}
@@ -247,10 +307,6 @@ parse_header (const uint8_t *p, const uint8_t *e, uint8_t *key, int keysize, uin
memcpy (value, p, sz);
value[sz] = 0;
- // skip linebreaks
- while (v < e && (*v == 0x0d || *v == 0x0a)) {
- v++;
- }
return v;
}
@@ -264,6 +320,16 @@ http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream)
uint8_t key[256];
uint8_t value[256];
while (p < end) {
+ if (p < end - 4) {
+ if (!memcmp (p, "\r\n\r\n", 4)) {
+ p += 4;
+ return size * nmemb - (size_t)(p-(const uint8_t *)ptr);
+ }
+ }
+ // skip linebreaks
+ while (p < end && (*p == 0x0d || *p == 0x0a)) {
+ p++;
+ }
p = parse_header (p, end, key, sizeof (key), value, sizeof (value));
trace ("%skey=%s value=%s\n", fp->icyheader ? "[icy] " : "", key, value);
if (!strcasecmp (key, "Content-Type")) {
@@ -287,11 +353,11 @@ http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream)
}
fp->content_genre = strdup (value);
}
-// else if (!strcasecmp (key, "icy-metaint")) {
-// //printf ("icy-metaint: %d\n", atoi (value));
-// fp->icy_metaint = atoi (value);
-// fp->wait_meta = fp->icy_metaint;
-// }
+ else if (!strcasecmp (key, "icy-metaint")) {
+ //printf ("icy-metaint: %d\n", atoi (value));
+ fp->icy_metaint = atoi (value);
+ fp->wait_meta = fp->icy_metaint;
+ }
}
if (!fp->icyheader) {
fp->gotsomeheader = 1;
@@ -302,7 +368,19 @@ http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream)
static int
http_curl_control (void *stream, double dltotal, double dlnow, double ultotal, double ulnow) {
HTTP_FILE *fp = (HTTP_FILE *)stream;
- trace ("http_curl_control, status = %d\n", fp ? fp->status : -1);
+
+ struct timeval tm;
+ gettimeofday (&tm, NULL);
+ float sec = tm.tv_sec - fp->last_read_time.tv_sec;
+ long response;
+ CURLcode code = curl_easy_getinfo (fp->curl, CURLINFO_RESPONSE_CODE, &response);
+ trace ("http_curl_control: status = %d, response = %d, interval: %f seconds\n", fp ? fp->status : -1, response, sec);
+ if (fp->status == STATUS_READING && sec > TIMEOUT) {
+ trace ("http_curl_control: timed out, restarting read\n");
+ memcpy (&fp->last_read_time, &tm, sizeof (struct timeval));
+ fp->remaining = 0;
+ fp->status = STATUS_SEEK;
+ }
assert (stream);
if (fp->status == STATUS_ABORTED) {
trace ("vfs_curl STATUS_ABORTED in progress callback\n");
@@ -322,12 +400,13 @@ http_thread_func (void *ctx) {
curl = curl_easy_init ();
fp->length = -1;
fp->status = STATUS_INITIAL;
+ fp->curl = curl;
int status;
trace ("vfs_curl: started loading data\n");
for (;;) {
-// struct curl_slist *headers = NULL;
+ struct curl_slist *headers = NULL;
curl_easy_reset (curl);
curl_easy_setopt (curl, CURLOPT_URL, fp->url);
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1);
@@ -344,8 +423,8 @@ http_thread_func (void *ctx) {
// enable up to 10 redirects
curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt (curl, CURLOPT_MAXREDIRS, 10);
-// headers = curl_slist_append (headers, "Icy-Metadata:1");
-// curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers);
+ headers = curl_slist_append (headers, "Icy-Metadata:1");
+ curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers);
if (fp->pos > 0) {
curl_easy_setopt (curl, CURLOPT_RESUME_FROM, fp->pos);
}
@@ -392,11 +471,37 @@ http_thread_func (void *ctx) {
deadbeef->mutex_unlock (fp->mutex);
break;
}
- fp->status = STATUS_INITIAL;
- trace ("seeking to %d\n", fp->pos);
+ else {
+ fp->skipbytes = 0;
+ fp->status = STATUS_INITIAL;
+ trace ("seeking to %d\n", fp->pos);
+ if (fp->length < 0) {
+ // icy -- need full restart
+ fp->pos = 0;
+ if (fp->content_type) {
+ free (fp->content_type);
+ fp->content_type = NULL;
+ }
+ if (fp->content_name) {
+ free (fp->content_name);
+ fp->content_name = NULL;
+ }
+ if (fp->content_genre) {
+ free (fp->content_genre);
+ fp->content_genre = NULL;
+ }
+ fp->seektoend = 0;
+ fp->gotheader = 0;
+ fp->icyheader = 0;
+ fp->gotsomeheader = 0;
+ fp->wait_meta = 0;
+ fp->icy_metaint = 0;
+ }
+ }
deadbeef->mutex_unlock (fp->mutex);
-// curl_slist_free_all (headers);
+ curl_slist_free_all (headers);
}
+ fp->curl = NULL;
curl_easy_cleanup (curl);
deadbeef->mutex_lock (fp->mutex);
@@ -465,8 +570,22 @@ http_read (void *ptr, size_t size, size_t nmemb, DB_FILE *stream) {
{
// wait until data is available
while ((fp->remaining == 0 || fp->skipbytes > 0) && fp->status != STATUS_FINISHED && !vfs_curl_abort) {
- trace ("vfs_curl: readwait..\n");
+// trace ("vfs_curl: readwait..\n");
deadbeef->mutex_lock (fp->mutex);
+ if (fp->status == STATUS_READING) {
+ struct timeval tm;
+ gettimeofday (&tm, NULL);
+ float sec = tm.tv_sec - fp->last_read_time.tv_sec;
+ if (sec > TIMEOUT) {
+ trace ("http_read: timed out, restarting read\n");
+ memcpy (&fp->last_read_time, &tm, sizeof (struct timeval));
+ fp->remaining = 0;
+ fp->status = STATUS_SEEK;
+ deadbeef->mutex_unlock (fp->mutex);
+ deadbeef->streamer_reset (1);
+ continue;
+ }
+ }
int skip = min (fp->remaining, fp->skipbytes);
if (skip > 0) {
// trace ("skipping %d bytes\n");
@@ -634,6 +753,7 @@ http_get_content_name (DB_FILE *stream) {
return NULL;
}
if (fp->gotheader) {
+ trace ("returning %s\n", fp->content_name);
return fp->content_name;
}
if (!fp->tid) {
diff --git a/streamer.c b/streamer.c
index 460b3d83..94779773 100644
--- a/streamer.c
+++ b/streamer.c
@@ -26,7 +26,6 @@
#endif
#include <sys/time.h>
#include "threading.h"
-#include "codec.h"
#include "playlist.h"
#include "common.h"
#include "streamer.h"
@@ -38,26 +37,24 @@
#include "volume.h"
#include "vfs.h"
-#define trace(...) { fprintf(stderr, __VA_ARGS__); }
-//#define trace(fmt,...)
+//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+#define trace(fmt,...)
static intptr_t streamer_tid;
static int src_quality;
static SRC_STATE *src;
static SRC_DATA srcdata;
-static int codecleft;
+static int src_remaining; // number of input samples in SRC buffer
static int conf_replaygain_mode = 0;
static int conf_replaygain_scale = 1;
-// that's buffer for resampling.
-// our worst case is 192KHz downsampling to 22050Hz with 2048 sample output buffer
-#define INPUT_BUFFER_SIZE (2048*192000/22050*8)
-static char g_readbuffer[INPUT_BUFFER_SIZE];
-// fbuffer contains readbuffer converted to floating point, to pass to SRC
-static float g_fbuffer[INPUT_BUFFER_SIZE];
-// output SRC buffer - can't really exceed INPUT_BUFFER_SIZE
-#define SRC_BUFFER_SIZE (INPUT_BUFFER_SIZE*2)
-static float g_srcbuffer[SRC_BUFFER_SIZE];
+
+#define SRC_BUFFER 16000
+static int src_in_remaining = 0;
+//static int16_t g_src_in_buffer[SRC_BUFFER*2];
+static float g_src_in_fbuffer[SRC_BUFFER*2];
+static float g_src_out_fbuffer[SRC_BUFFER*2];
+
static int streaming_terminate;
// buffer up to 3 seconds at 44100Hz stereo
@@ -260,7 +257,7 @@ streamer_thread (void *ctx) {
#ifdef __linux__
prctl (PR_SET_NAME, "deadbeef-stream", 0, 0, 0, 0);
#endif
- codecleft = 0;
+ src_remaining = 0;
while (!streaming_terminate) {
struct timeval tm1;
@@ -270,9 +267,7 @@ streamer_thread (void *ctx) {
int sng = nextsong;
int pstate = nextsong_pstate;
nextsong = -1;
- codec_lock ();
- codecleft = 0;
- codec_unlock ();
+ src_remaining = 0;
if (badsong == sng) {
trace ("looped to bad file. stopping...\n");
streamer_set_nextsong (-2, 1);
@@ -456,9 +451,7 @@ streamer_thread (void *ctx) {
streamer_lock ();
streambuffer_fill = 0;
streambuffer_pos = 0;
- codec_lock ();
- codecleft = 0;
- codec_unlock ();
+ src_remaining = 0;
if (str_current_decoder->plugin->seek (str_current_decoder, pos) >= 0) {
playpos = str_current_decoder->readpos;
}
@@ -551,7 +544,7 @@ streamer_free (void) {
void
streamer_reset (int full) { // must be called when current song changes by external reasons
- codecleft = 0;
+ src_remaining = 0;
if (full) {
streambuffer_pos = 0;
streambuffer_fill = 0;
@@ -702,128 +695,206 @@ float32_to_int16 (float *in, int16_t *out, int nsamples) {
fpu_restore (ctl);
}
+/*
+
+ src algorithm
+
+ initsize = size;
+ while (size > 0) {
+ need_frames = SRC_BUFFER - src_remaining
+ read_samples (need_frames)
+ src_process (output_frames)
+ convert_to_int16 (bytes, size)
+ // handle errors
+ }
+ return initsize-size;
+
+*/
+
+static int
+streamer_read_data_for_src (int16_t *buffer, int frames) {
+ DB_decoder_t *decoder = str_current_decoder->plugin;
+ int channels = str_current_decoder->channels;
+ if (channels > 2) {
+ channels = 2;
+ }
+ int bytesread = decoder->read_int16 (str_current_decoder, (uint8_t*)buffer, frames * sizeof (int16_t) * channels);
+ if (channels == 1) {
+ // convert to stereo
+ int n = frames-1;
+ while (n >= 0) {
+ buffer[n*2+0] = buffer[n];
+ buffer[n*2+1] = buffer[n];
+ }
+ }
+ return bytesread / (sizeof (int16_t) * channels);
+}
+
+static int
+streamer_read_data_for_src_float (float *buffer, int frames) {
+ DB_decoder_t *decoder = str_current_decoder->plugin;
+ int channels = str_current_decoder->channels;
+ if (channels > 2) {
+ channels = 2;
+ }
+ if (decoder->read_float32) {
+// trace ("call read_float32 (%d frames -> %d bytes)\n", frames, frames * sizeof (float) * channels);
+ int bytesread = decoder->read_float32 (str_current_decoder, (uint8_t*)buffer, frames * sizeof (float) * channels);
+// trace ("got %d bytes -> %d frames\n", bytesread, bytesread / (sizeof (float) * channels));
+ if (channels == 1) {
+ // convert to stereo
+ int n = frames-1;
+ while (n >= 0) {
+ buffer[n*2+0] = buffer[n];
+ buffer[n*2+1] = buffer[n];
+ }
+ bytesread *= 2;
+ }
+ return bytesread / (sizeof (float) * channels);
+ }
+// trace ("read_float32 not impl\n");
+ int16_t intbuffer[frames*2];
+ int res = streamer_read_data_for_src (intbuffer, frames);
+ for (int i = 0; i < res; i++) {
+ buffer[i*2+0] = intbuffer[i*2+0]/(float)0x7fff;
+ buffer[i*2+1] = intbuffer[i*2+1]/(float)0x7fff;
+ }
+ return res;
+}
+
+static int
+streamer_decode_src_libsamplerate (uint8_t *bytes, int size) {
+ int initsize = size;
+ int16_t *out = (int16_t *)bytes;
+ DB_decoder_t *decoder = str_current_decoder->plugin;
+ int samplerate = str_current_decoder->samplerate;
+ float ratio = (float)p_get_rate ()/samplerate;
+ while (size > 0) {
+ int n_output_frames = size / sizeof (int16_t) / 2;
+ int n_input_frames = n_output_frames * samplerate / p_get_rate () + 100;
+ // read data from decoder
+ if (n_input_frames > SRC_BUFFER - src_remaining ) {
+ n_input_frames = SRC_BUFFER - src_remaining;
+ }
+ int nread = streamer_read_data_for_src_float (&g_src_in_fbuffer[src_remaining*2], n_input_frames);
+ src_remaining += nread;
+ // resample
+
+ srcdata.data_in = g_src_in_fbuffer;
+ srcdata.data_out = g_src_out_fbuffer;
+ srcdata.input_frames = src_remaining;
+ srcdata.output_frames = size/4;
+ srcdata.src_ratio = ratio;
+// trace ("SRC from %d to %d\n", samplerate, p_get_rate ());
+ srcdata.end_of_input = (nread == n_input_frames) ? 0 : 1;
+ src_process (src, &srcdata);
+ // convert back to s16 format
+// trace ("out frames: %d\n", srcdata.output_frames_gen);
+ int genbytes = srcdata.output_frames_gen * 4;
+ int bytesread = min(size, genbytes);
+// trace ("bytesread: %d\n", bytesread);
+ float32_to_int16 ((float*)g_src_out_fbuffer, (int16_t*)bytes, bytesread>>1);
+ size -= bytesread;
+// trace ("size: %d\n", size);
+ bytes += bytesread;
+ // calculate how many unused input samples left
+ src_remaining -= srcdata.input_frames_used;
+
+ // copy spare samples for next update
+ if (src_remaining > 0) {
+ memmove (g_src_in_fbuffer, &g_src_in_fbuffer[srcdata.input_frames_used*2], src_remaining * sizeof (float) * 2);
+ }
+
+ if (nread != n_input_frames) {
+ trace ("nread=%d, n_input_frames=%d, mismatch!\n", nread, n_input_frames);
+ break;
+ }
+ }
+ return initsize-size;
+}
+
+#if 0
+static int
+streamer_decode_src (uint8_t *bytes, int size) {
+ int initsize = size;
+ int16_t *out = (int16_t *)bytes;
+ DB_decoder_t *decoder = str_streaming_song.decoder;
+ int samplerate = decoder->info.samplerate;
+ float ratio = (float)samplerate / p_get_rate ();
+ while (size > 0) {
+ int n_output_frames = size / sizeof (int16_t) / 2;
+ int n_input_frames = n_output_frames * samplerate / p_get_rate () + 100;
+ // read data from decoder
+ if (n_input_frames > SRC_BUFFER - src_remaining ) {
+ n_input_frames = SRC_BUFFER - src_remaining;
+ }
+ int nread = streamer_read_data_for_src (&g_src_in_buffer[src_remaining*2], n_input_frames);
+ src_remaining += nread;
+ // resample
+ float idx = 0;
+ int i = 0;
+ while (i < n_output_frames && idx < src_remaining) {
+ int k = (int)idx;
+ out[0] = g_src_in_buffer[k*2+0];
+ out[1] = g_src_in_buffer[k*2+1];
+ i++;
+ idx += ratio;
+ out += 2;
+ }
+ size -= i*4;
+ src_remaining -= idx;
+ if (src_remaining < 0) {
+ src_remaining = 0;
+ }
+ else if (src_remaining > 0) {
+ memmove (g_src_in_buffer, &g_src_in_buffer[(SRC_BUFFER-src_remaining)*2], src_remaining*2*sizeof (int16_t));
+ }
+ if (nread != n_input_frames) {
+ break;
+ }
+ }
+ return initsize-size;
+}
+#endif
+
// returns number of bytes been read
static int
streamer_read_async (char *bytes, int size) {
int initsize = size;
for (;;) {
int bytesread = 0;
- codec_lock ();
if (!str_current_decoder) {
- codec_unlock ();
- break;
- }
- DB_decoder_t *decoder = str_current_decoder->plugin;
- if (!decoder) {
// means there's nothing left to stream, so just do nothing
- codec_unlock ();
break;
}
if (str_current_decoder->samplerate != -1) {
int nchannels = str_current_decoder->channels;
+ if (nchannels > 2) {
+ nchannels = 2;
+ }
int samplerate = str_current_decoder->samplerate;
- if (str_current_decoder->samplerate == p_get_rate () && decoder->read_int16) {
+ if (str_current_decoder->samplerate == p_get_rate () && str_current_decoder->plugin->read_int16) {
// samplerate match
- if (str_current_decoder->channels == 2) {
- bytesread = decoder->read_int16 (str_current_decoder, bytes, size);
+ if (nchannels == 2) {
+ bytesread = str_current_decoder->plugin->read_int16 (str_current_decoder, bytes, size);
apply_replay_gain_int16 (&str_streaming_song, bytes, size);
- codec_unlock ();
}
else {
- bytesread = decoder->read_int16 (str_current_decoder, g_readbuffer, size>>1);
- apply_replay_gain_int16 (&str_streaming_song, g_readbuffer, size>>1);
- mono_int16_to_stereo_int16 ((int16_t*)g_readbuffer, (int16_t*)bytes, size>>2);
+ uint8_t buffer[size>>1];
+ bytesread = str_current_decoder->plugin->read_int16 (str_current_decoder, buffer, size>>1);
+ apply_replay_gain_int16 (&str_streaming_song, buffer, size>>1);
+ mono_int16_to_stereo_int16 ((int16_t*)buffer, (int16_t*)bytes, size>>2);
bytesread *= 2;
- codec_unlock ();
}
}
else if (src_is_valid_ratio (p_get_rate ()/(double)samplerate)) {
- // read and do SRC
- int nsamples = size/4;
- nsamples = nsamples * samplerate / p_get_rate () * 2;
- // read data at source samplerate (with some room for SRC)
- int nbytes = (nsamples - codecleft) * 2 * nchannels;
- int samplesize = 2;
- if (nbytes <= 0) {
- nbytes = 0;
- }
- else {
- if (!decoder->read_float32) {
- if (nbytes > INPUT_BUFFER_SIZE) {
- trace ("input buffer overflow\n");
- nbytes = INPUT_BUFFER_SIZE;
- }
- bytesread = decoder->read_int16 (str_current_decoder, g_readbuffer, nbytes);
- apply_replay_gain_int16 (&str_streaming_song, g_readbuffer, nbytes);
- }
- else {
- samplesize = 4;
- }
- }
- codec_unlock ();
- // recalculate nsamples according to how many bytes we've got
- if (nbytes != 0) {
- if (!decoder->read_float32) {
- nsamples = bytesread / (samplesize * nchannels) + codecleft;
- // convert to float
- float *fbuffer = g_fbuffer + codecleft*2;
- int n = nsamples - codecleft;
- if (nchannels == 2) {
- n <<= 1;
- int16_to_float32 ((int16_t*)g_readbuffer, fbuffer, n);
- }
- else if (nchannels == 1) { // convert mono to stereo
- mono_int16_to_stereo_float32 ((int16_t*)g_readbuffer, fbuffer, n);
- }
- }
- else {
- float *fbuffer = g_fbuffer + codecleft*2;
- if (nchannels == 1) {
- codec_lock ();
- bytesread = decoder->read_float32 (str_current_decoder, g_readbuffer, nbytes*2);
- codec_unlock ();
- apply_replay_gain_float32 (&str_streaming_song, g_readbuffer, nbytes*2);
- nsamples = bytesread / (samplesize * nchannels) + codecleft;
- mono_float32_to_stereo_float32 ((float *)g_readbuffer, fbuffer, nsamples-codecleft);
- }
- else {
- codec_lock ();
- bytesread = decoder->read_float32 (str_current_decoder, (char *)fbuffer, nbytes*2);
- codec_unlock ();
- apply_replay_gain_float32 (&str_streaming_song, (char *)fbuffer, nbytes*2);
- nsamples = bytesread / (samplesize * nchannels) + codecleft;
- }
- }
- }
- //codec_lock ();
- // convert samplerate
- mutex_lock (decodemutex);
- srcdata.data_in = g_fbuffer;
- srcdata.data_out = g_srcbuffer;
- srcdata.input_frames = nsamples;
- srcdata.output_frames = size/4;
- srcdata.src_ratio = p_get_rate ()/(double)samplerate;
- srcdata.end_of_input = 0;
- // src_set_ratio (src, srcdata.src_ratio);
- src_process (src, &srcdata);
- //codec_unlock ();
- // convert back to s16 format
- nbytes = size;
- int genbytes = srcdata.output_frames_gen * 4;
- bytesread = min(size, genbytes);
- float32_to_int16 ((float*)g_srcbuffer, (int16_t*)bytes, bytesread>>1);
- // calculate how many unused input samples left
- codecleft = nsamples - srcdata.input_frames_used;
- mutex_unlock (decodemutex);
- // copy spare samples for next update
- memmove (g_fbuffer, &g_fbuffer[srcdata.input_frames_used*2], codecleft * 8);
+ bytesread = streamer_decode_src_libsamplerate (bytes, size);
}
else {
fprintf (stderr, "invalid ratio! %d / %d = %f", p_get_rate (), samplerate, p_get_rate ()/(float)samplerate);
}
}
else {
- codec_unlock ();
}
bytes += bytesread;
size -= bytesread;
diff --git a/web/faq.txt b/web/faq.txt
index 906050af..f53c0b6e 100644
--- a/web/faq.txt
+++ b/web/faq.txt
@@ -1,59 +1,59 @@
-A: I have PulseAudio, and I have playback problems (noise, distortion, wrong speed, no sound, etc)
+Q: I have PulseAudio, and I have playback problems (noise, distortion, wrong speed, no sound, etc)
-Q: Disable PulseAudio
+A: Disable PulseAudio
--
-A: I don't get it. Yes, indeed I have PulseAudio, but in Deadbeef I select ALSA output, and I get playback problems.
+Q: I don't get it. Yes, indeed I have PulseAudio, but in Deadbeef I select ALSA output, and I get playback problems.
-Q: PulseAudio tricks applications by providing ALSA-plugin that routes sound from application to PulseAudio via ALSA API.
+A: PulseAudio tricks applications by providing ALSA-plugin that routes sound from application to PulseAudio via ALSA API.
--
-A: Is it planned to support PulseAudio natively?
+Q: Is it planned to support PulseAudio natively?
-Q: No one works on that right now. You can write the output plugin yourself. Send me the patch, I'll include it in the next release.
+A: No one works on that right now. You can write the output plugin yourself. Send me the patch, I'll include it in the next release.
--
-A: I don't like your tone.
+Q: I don't like your tone.
-Q: GTFO!
+A: GTFO!
--
-A: Is it planned to support multiple playlists and EQ?
+Q: Is it planned to support multiple playlists and EQ?
-Q: Yes
+A: Yes
--
-A: Is it planned to make Windows and/or OSX ports?
+Q: Is it planned to make Windows and/or OSX ports?
-Q: No
+A: No
--
-A: Playlist, settings, etc are not saved if I kill X session, shut down or restart computer, etc
+Q: Playlist, settings, etc are not saved if I kill X session, shut down or restart computer, etc
-Q: That is not fixed yet. But it is work in progress. Please don't bother me with that unless you have a patch that fixes it.
+A: That is not fixed yet. But it is work in progress. Please don't bother me with that unless you have a patch that fixes it.
--
-A: Deadbeef takes too much CPU% when playing mp3/ogg/flac.
+Q: Deadbeef takes too much CPU% when playing mp3/ogg/flac.
-Q: Try to reduce SRC quality in preferences window
-Q: Try to check "Use software ALSA resampling" box
-Q: See the PulseAudio questions above
+A: Try to reduce SRC quality in preferences window
+A: Try to check "Use software ALSA resampling" box
+A: See the PulseAudio questions above
--
-A: Deadbeef takes too much CPU% when playing APE files.
+Q: Deadbeef takes too much CPU% when playing APE files.
-Q: That's normal. Consider switching to wavpack or flac.
+A: That's normal. Consider switching to wavpack or flac.
--
-A: I have 50 ideas about what features must be added to the player!!!111oneone. Can i become idea-generator of the project, so that I tell, and developers do what I ask for?
+Q: I have 50 ideas about what features must be added to the player!!!111oneone. Can i become idea-generator of the project, so that I tell, and developers do what I ask for?
-Q: No
+A: No