/*
DeaDBeeF - ultimate music player for GNU/Linux systems with X11
Copyright (C) 2009 Alexey Yakovenko
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 .
*/
#include
#include
#include
#include
#include "deadbeef.h"
static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
static FLAC__StreamDecoder *decoder = 0;
#define BUFFERSIZE 100000
static char buffer[BUFFERSIZE]; // this buffer always has int32 samples
static int remaining; // bytes remaining in buffer from last read
static float timestart;
static float timeend;
static FLAC__StreamDecoderWriteStatus
cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const inputbuffer[], void *client_data) {
if (frame->header.blocksize == 0) {
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
int readbytes = frame->header.blocksize * plugin.info.channels * plugin.info.bps / 8;
int bufsize = BUFFERSIZE-remaining;
int bufsamples = bufsize / (plugin.info.channels * plugin.info.bps / 8);
int nsamples = min (bufsamples, frame->header.blocksize);
char *bufptr = &buffer[remaining];
int32_t mask = (1<<(frame->header.bits_per_sample-1))-1;
float scaler = 1.f / ((1<<(frame->header.bits_per_sample-1))-1);
int32_t neg = 1<header.bits_per_sample;
int32_t sign = (1<<31);
int32_t signshift = frame->header.bits_per_sample-1;
for (int i = 0; i < nsamples; i++) {
int32_t sample = inputbuffer[0][i];
((float*)bufptr)[0] = (sample - ((sample&sign)>>signshift)*neg) * scaler;
bufptr += sizeof (float);
remaining += sizeof (float);
if (plugin.info.channels > 1) {
int32_t sample = inputbuffer[1][i];
((float*)bufptr)[0] = (sample - ((sample&sign)>>signshift)*neg) * scaler;
bufptr += sizeof (float);
remaining += sizeof (float);
}
}
if (readbytes > bufsize) {
printf ("flac: buffer overflow, distortion will occur\n");
// return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
static void
cflac_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
FLAC__uint64 total_samples = metadata->data.stream_info.total_samples;
int sample_rate = metadata->data.stream_info.sample_rate;
int channels = metadata->data.stream_info.channels;
int bps = metadata->data.stream_info.bits_per_sample;
plugin.info.samplerate = sample_rate;
plugin.info.channels = channels;
plugin.info.bps = bps;
plugin.info.readpos = 0;
}
static void
cflac_error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
// fprintf(stderr, "cflac: got error callback: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
}
static int cflac_init_stop_decoding;
static void
cflac_init_error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
// fprintf(stderr, "cflac: got error callback: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
cflac_init_stop_decoding = 1;
}
static void
cflac_free (void);
static int
cflac_init (DB_playItem_t *it) {
FILE *fp = fopen (it->fname, "rb");
if (!fp) {
return -1;
}
char sign[4];
if (fread (sign, 1, 4, fp) != 4) {
fclose (fp);
return -1;
}
if (strncmp (sign, "fLaC", 4)) {
fclose (fp);
return -1;
}
fclose (fp);
fp = NULL;
FLAC__StreamDecoderInitStatus status;
decoder = FLAC__stream_decoder_new();
if (!decoder) {
// printf ("FLAC__stream_decoder_new failed\n");
return -1;
}
FLAC__stream_decoder_set_md5_checking(decoder, 0);
status = FLAC__stream_decoder_init_file(decoder, it->fname, cflac_write_callback, cflac_metadata_callback, cflac_error_callback, NULL);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
cflac_free ();
return -1;
}
plugin.info.samplerate = -1;
if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder)) {
cflac_free ();
return -1;
}
if (plugin.info.samplerate == -1) { // not a FLAC stream
cflac_free ();
return -1;
}
timestart = it->timestart;
timeend = it->timeend;
if (timeend > timestart || timeend < 0) {
plugin.seek (0);
}
plugin.info.readpos = 0;
remaining = 0;
return 0;
}
static void
cflac_free (void) {
if (decoder) {
FLAC__stream_decoder_delete(decoder);
decoder = NULL;
}
}
static int
cflac_read_int16 (char *bytes, int size) {
int initsize = size;
int nsamples = size / (plugin.info.channels * plugin.info.bps / 8);
if (timeend > timestart) {
if (plugin.info.readpos + timestart > timeend) {
return 0;
}
}
do {
if (remaining) {
int s = size * 2;
int sz = min (remaining, s);
// convert from float to int16
float *in = (float *)buffer;
for (int i = 0; i < sz/4; i++) {
*((int16_t *)bytes) = (int16_t)((*in) * 0x7fff);
size -= 2;
bytes += 2;
in++;
}
if (sz < remaining) {
memmove (buffer, &buffer[sz], remaining-sz);
}
remaining -= sz;
plugin.info.readpos += (float)sz / (plugin.info.channels * plugin.info.samplerate * sizeof (float));
if (timeend > timestart) {
if (plugin.info.readpos + timestart > timeend) {
break;
}
}
}
if (!size) {
break;
}
if (!FLAC__stream_decoder_process_single (decoder)) {
break;
}
if (FLAC__stream_decoder_get_state (decoder) == FLAC__STREAM_DECODER_END_OF_STREAM) {
break;
}
} while (size > 0);
return initsize - size;
}
static int
cflac_read_float32 (char *bytes, int size) {
int initsize = size;
int nsamples = size / (plugin.info.channels * plugin.info.bps / 8);
if (timeend > timestart) {
if (plugin.info.readpos + timestart > timeend) {
fprintf (stderr, "readpos %f + timestart %f = %f is > timeend %f\n", plugin.info.readpos, timestart, plugin.info.readpos + timestart, timeend);
return 0;
}
}
do {
if (remaining) {
int sz = min (remaining, size);
memcpy (bytes, buffer, sz);
size -= sz;
bytes += sz;
if (sz < remaining) {
memmove (buffer, &buffer[sz], remaining-sz);
}
remaining -= sz;
plugin.info.readpos += (float)sz / (plugin.info.channels * plugin.info.samplerate * sizeof (int32_t));
if (timeend > timestart) {
if (plugin.info.readpos + timestart > timeend) {
break;
}
}
}
if (!size) {
break;
}
if (!FLAC__stream_decoder_process_single (decoder)) {
break;
}
if (FLAC__stream_decoder_get_state (decoder) == FLAC__STREAM_DECODER_END_OF_STREAM) {
break;
}
} while (size > 0);
return initsize - size;
}
static int
cflac_seek (float time) {
time += timestart;
if (!FLAC__stream_decoder_seek_absolute (decoder, (FLAC__uint64)(time * plugin.info.samplerate))) {
return -1;
}
remaining = 0;
plugin.info.readpos = time - timestart;
printf ("readpos: %f\n", plugin.info.readpos);
return 0;
}
static FLAC__StreamDecoderWriteStatus
cflac_init_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const inputbuffer[], void *client_data) {
if (frame->header.blocksize == 0 || cflac_init_stop_decoding) {
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
typedef struct {
DB_playItem_t *after;
DB_playItem_t *last;
const char *fname;
int samplerate;
int nchannels;
float duration;
} cue_cb_data_t;
static void
cflac_init_cue_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
if (cflac_init_stop_decoding) {
return;
}
cue_cb_data_t *cb = (cue_cb_data_t *)client_data;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
cb->samplerate = metadata->data.stream_info.sample_rate;
cb->nchannels = metadata->data.stream_info.channels;
cb->duration = metadata->data.stream_info.total_samples / (float)metadata->data.stream_info.sample_rate;
}
// {{{ disabled
#if 0
else if (metadata->type == FLAC__METADATA_TYPE_CUESHEET) {
//printf ("loading embedded cuesheet!\n");
const FLAC__StreamMetadata_CueSheet *cue = &metadata->data.cue_sheet;
DB_playItem_t *prev = NULL;
for (int i = 0; i < cue->num_tracks; i++) {
FLAC__StreamMetadata_CueSheet_Track *t = &cue->tracks[i];
if (t->type == 0 && t->num_indices > 0) {
FLAC__StreamMetadata_CueSheet_Index *idx = t->indices;
DB_playItem_t *it = malloc (sizeof (DB_playItem_t));
memset (it, 0, sizeof (DB_playItem_t));
it->decoder = &plugin;
it->fname = strdup (cb->fname);
it->tracknum = t->number;
it->timestart = (float)t->offset / cb->samplerate;
it->timeend = -1; // will be filled by next read
if (prev) {
prev->timeend = it->timestart;
prev->duration = prev->timeend - prev->timestart;
}
it->filetype = "FLAC";
//printf ("N: %d, t: %f, bps=%d\n", it->tracknum, it->timestart/60.f, cb->samplerate);
DB_playItem_t *ins = deadbeef->pl_insert_item (cb->last, it);
if (ins) {
cb->last = ins;
}
else {
deadbeef->pl_item_free (it);
it = NULL;
}
prev = it;
}
}
if (prev) {
prev->timeend = cb->duration;
prev->duration = prev->timeend - prev->timestart;
}
}
else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
DB_playItem_t *it = NULL;
if (cb->after) {
it = cb->after->next[PL_MAIN];
}
else if (cb->last) {
it = playlist_head[PL_MAIN];
}
if (it) {
for (; it != cb->last->next[PL_MAIN]; it = it->next[PL_MAIN]) {
char str[10];
snprintf (str, 10, "%d", it->tracknum);
pl_add_meta (it, "track", str);
pl_add_meta (it, "title", NULL);
}
}
}
#endif
// }}}
else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
const FLAC__StreamMetadata_VorbisComment *vc = &metadata->data.vorbis_comment;
for (int i = 0; i < vc->num_comments; i++) {
const FLAC__StreamMetadata_VorbisComment_Entry *c = &vc->comments[i];
if (c->length > 0) {
char s[c->length+1];
s[c->length] = 0;
memcpy (s, c->entry, c->length);
if (!strncasecmp (s, "cuesheet=", 9)) {
cb->last = deadbeef->pl_insert_cue_from_buffer (cb->after, cb->fname, s+9, c->length-9, &plugin, "FLAC");
}
}
}
}
}
static void
cflac_init_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
if (cflac_init_stop_decoding) {
printf ("error flag is set, ignoring init_metadata callback..\n");
return;
}
DB_playItem_t *it = (DB_playItem_t *)client_data;
//it->tracknum = 0;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
it->duration = metadata->data.stream_info.total_samples / (float)metadata->data.stream_info.sample_rate;
}
else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
const FLAC__StreamMetadata_VorbisComment *vc = &metadata->data.vorbis_comment;
int title_added = 0;
for (int i = 0; i < vc->num_comments; i++) {
const FLAC__StreamMetadata_VorbisComment_Entry *c = &vc->comments[i];
if (c->length > 0) {
char s[c->length+1];
s[c->length] = 0;
memcpy (s, c->entry, c->length);
if (!strncasecmp (s, "ARTIST=", 7)) {
deadbeef->pl_add_meta (it, "artist", s + 7);
}
else if (!strncasecmp (s, "TITLE=", 6)) {
deadbeef->pl_add_meta (it, "title", s + 6);
title_added = 1;
}
else if (!strncasecmp (s, "ALBUM=", 6)) {
deadbeef->pl_add_meta (it, "album", s + 6);
}
else if (!strncasecmp (s, "TRACKNUMBER=", 12)) {
deadbeef->pl_add_meta (it, "track", s + 12);
}
else if (!strncasecmp (s, "DATE=", 5)) {
deadbeef->pl_add_meta (it, "date", s + 5);
}
}
}
if (!title_added) {
deadbeef->pl_add_meta (it, "title", NULL);
}
// pl_add_meta (it, "artist", performer);
// pl_add_meta (it, "album", albumtitle);
// pl_add_meta (it, "track", track);
// pl_add_meta (it, "title", title);
}
// int *psr = (int *)client_data;
// *psr = metadata->data.stream_info.sample_rate;
}
static DB_playItem_t *
cflac_insert (DB_playItem_t *after, const char *fname) {
DB_playItem_t *it = NULL;
FLAC__StreamDecoder *decoder = NULL;
FILE *fp = fopen (fname, "rb");
if (!fp) {
goto cflac_insert_fail;
}
char sign[4];
if (fread (sign, 1, 4, fp) != 4) {
goto cflac_insert_fail;
}
if (strncmp (sign, "fLaC", 4)) {
goto cflac_insert_fail;
}
fclose (fp);
fp = NULL;
cflac_init_stop_decoding = 0;
//try embedded cue, and calculate duration
FLAC__StreamDecoderInitStatus status;
decoder = FLAC__stream_decoder_new();
if (!decoder) {
goto cflac_insert_fail;
}
FLAC__stream_decoder_set_md5_checking(decoder, 0);
// try embedded cue
cue_cb_data_t cb = {
.fname = fname,
.after = after,
.last = after
};
FLAC__stream_decoder_set_metadata_respond_all (decoder);
status = FLAC__stream_decoder_init_file (decoder, fname, cflac_init_write_callback, cflac_init_cue_metadata_callback, cflac_init_error_callback, &cb);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK || cflac_init_stop_decoding) {
goto cflac_insert_fail;
}
if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder) || cflac_init_stop_decoding) {
goto cflac_insert_fail;
}
FLAC__stream_decoder_delete(decoder);
decoder = NULL;
if (cb.last != after) {
// printf ("embedded cue found!\n");
return cb.last;
}
// try external cue
DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "flac");
if (cue_after) {
cue_after->timeend = cb.duration;
cue_after->duration = cue_after->timeend - cue_after->timestart;
return cue_after;
}
decoder = FLAC__stream_decoder_new();
if (!decoder) {
goto cflac_insert_fail;
}
FLAC__stream_decoder_set_md5_checking(decoder, 0);
// try single FLAC file without cue
FLAC__stream_decoder_set_metadata_respond_all (decoder);
int samplerate = -1;
it = deadbeef->pl_item_alloc ();
it->decoder = &plugin;
it->fname = strdup (fname);
it->tracknum = 0;
it->timestart = 0;
it->timeend = 0;
status = FLAC__stream_decoder_init_file (decoder, fname, cflac_init_write_callback, cflac_init_metadata_callback, cflac_init_error_callback, it);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK || cflac_init_stop_decoding) {
goto cflac_insert_fail;
}
if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder) || cflac_init_stop_decoding) {
goto cflac_insert_fail;
}
FLAC__stream_decoder_delete(decoder);
decoder = NULL;
it->filetype = "FLAC";
after = deadbeef->pl_insert_item (after, it);
return after;
cflac_insert_fail:
if (it) {
deadbeef->pl_item_free (it);
}
if (decoder) {
FLAC__stream_decoder_delete(decoder);
}
if (fp) {
fclose (fp);
}
return NULL;
}
static const char *exts[] = { "flac", NULL };
static const char *filetypes[] = { "FLAC", NULL };
// define plugin interface
static DB_decoder_t plugin = {
.plugin.version_major = 0,
.plugin.version_minor = 1,
.plugin.type = DB_PLUGIN_DECODER,
.plugin.name = "FLAC decoder",
.plugin.author = "Alexey Yakovenko",
.plugin.email = "waker@users.sourceforge.net",
.plugin.website = "http://deadbeef.sf.net",
.init = cflac_init,
.free = cflac_free,
.read_int16 = cflac_read_int16,
.read_float32 = cflac_read_float32,
.seek = cflac_seek,
.insert = cflac_insert,
.exts = exts,
.id = "stdflac",
.filetypes = filetypes
};
DB_plugin_t *
flac_load (DB_functions_t *api) {
deadbeef = api;
return DB_PLUGIN (&plugin);
}