diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | codec.c | 42 | ||||
-rw-r--r-- | codec.h | 65 | ||||
-rw-r--r-- | deadbeef.h | 3 | ||||
-rw-r--r-- | dumb/dumb-kode54/include/dumb.h | 2 | ||||
-rw-r--r-- | dumb/dumb-kode54/src/core/dumbfile.c | 2 | ||||
-rw-r--r-- | dumb/dumb-kode54/src/helpers/stdfile.c | 4 | ||||
-rw-r--r-- | main.c | 4 | ||||
-rw-r--r-- | playlist.c | 170 | ||||
-rw-r--r-- | playlist.h | 9 | ||||
-rw-r--r-- | plugins.c | 3 | ||||
-rw-r--r-- | plugins/alsa/alsa.c | 10 | ||||
-rw-r--r-- | plugins/flac/flac.c | 77 | ||||
-rw-r--r-- | plugins/mpgmad/mpgmad.c | 133 | ||||
-rw-r--r-- | plugins/vfs_curl/vfs_curl.c | 308 | ||||
-rw-r--r-- | streamer.c | 303 | ||||
-rw-r--r-- | web/faq.txt | 44 |
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 @@ -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,
@@ -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 (); @@ -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"); @@ -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 @@ -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) { @@ -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 |