diff options
author | Alexey Yakovenko <wakeroid@gmail.com> | 2009-09-05 15:04:44 +0200 |
---|---|---|
committer | Alexey Yakovenko <wakeroid@gmail.com> | 2009-09-05 15:04:44 +0200 |
commit | 1397058efc15e9cab30c572be63b37e8af34e07a (patch) | |
tree | 7fa180198f8feede35dbd9ae5aece54fba497e24 /plugins | |
parent | 2e81b08e5877ef2ce4a7ca163eec552f5d72ed94 (diff) |
moved flac support to plugin
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/flac/Makefile.am | 7 | ||||
-rw-r--r-- | plugins/flac/flac.c | 530 |
2 files changed, 537 insertions, 0 deletions
diff --git a/plugins/flac/Makefile.am b/plugins/flac/Makefile.am new file mode 100644 index 00000000..3072883e --- /dev/null +++ b/plugins/flac/Makefile.am @@ -0,0 +1,7 @@ +flacdir = $(libdir)/$(PACKAGE) +pkglib_LTLIBRARIES = flac.la +flac_la_SOURCES = flac.c +flac_la_LDFLAGS = -module + +flac_la_LIBADD = $(LDADD) $(FLAC_LIBS) +AM_CFLAGS = $(CFLAGS) -std=c99 diff --git a/plugins/flac/flac.c b/plugins/flac/flac.c new file mode 100644 index 00000000..3cf9dd8d --- /dev/null +++ b/plugins/flac/flac.c @@ -0,0 +1,530 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <FLAC/stream_decoder.h> +#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<<frame->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) { + fprintf (stderr, "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) { + 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; + 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", cb->duration); + } + } + } + } +} + +static void +cflac_init_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) { + if (cflac_init_stop_decoding) { + fprintf (stderr, "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) { + // that means embedded cue is loaded + return cb.last; + } + + // try external cue + DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "flac", cb.duration); + 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); +} |