diff options
Diffstat (limited to 'src/decoder/flac_plugin.c')
-rw-r--r-- | src/decoder/flac_plugin.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c new file mode 100644 index 000000000..7b9fce27d --- /dev/null +++ b/src/decoder/flac_plugin.c @@ -0,0 +1,459 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "_flac_common.h" +#include "../utils.h" +#include "../log.h" + +#include <assert.h> + +/* this code was based on flac123, from flac-tools */ + +static flac_read_status flacRead(mpd_unused const flac_decoder * flacDec, + FLAC__byte buf[], + flac_read_status_size_t *bytes, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + size_t r; + + r = decoder_read(data->decoder, data->inStream, (void *)buf, *bytes); + *bytes = r; + + if (r == 0) { + if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || + inputStreamAtEOF(data->inStream)) + return flac_read_status_eof; + else + return flac_read_status_abort; + } + + return flac_read_status_continue; +} + +static flac_seek_status flacSeek(mpd_unused const flac_decoder * flacDec, + FLAC__uint64 offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) { + return flac_seek_status_error; + } + + return flac_seek_status_ok; +} + +static flac_tell_status flacTell(mpd_unused const flac_decoder * flacDec, + FLAC__uint64 * offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *offset = (long)(data->inStream->offset); + + return flac_tell_status_ok; +} + +static flac_length_status flacLength(mpd_unused const flac_decoder * flacDec, + FLAC__uint64 * length, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *length = (size_t) (data->inStream->size); + + return flac_length_status_ok; +} + +static FLAC__bool flacEOF(mpd_unused const flac_decoder * flacDec, void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && + decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || + inputStreamAtEOF(data->inStream); +} + +static void flacError(mpd_unused const flac_decoder *dec, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("flac", status, (FlacData *) fdata); +} + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__SEEKABLE_STREAM_DECODER_OK: + case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + str = "read error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + str = "seekable stream error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + str = "decoder already initialized"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + str = "invalid callback"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + ERROR("flac %s\n", str); +} + +static int flac_init(FLAC__SeekableStreamDecoder *dec, + FLAC__SeekableStreamDecoderReadCallback read_cb, + FLAC__SeekableStreamDecoderSeekCallback seek_cb, + FLAC__SeekableStreamDecoderTellCallback tell_cb, + FLAC__SeekableStreamDecoderLengthCallback length_cb, + FLAC__SeekableStreamDecoderEofCallback eof_cb, + FLAC__SeekableStreamDecoderWriteCallback write_cb, + FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, + FLAC__SeekableStreamDecoderErrorCallback error_cb, + void *data) +{ + int s = 1; + s &= FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb); + s &= FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb); + s &= FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb); + s &= FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb); + s &= FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb); + s &= FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb); + s &= FLAC__seekable_stream_decoder_set_metadata_callback(dec, + metadata_cb); + s &= FLAC__seekable_stream_decoder_set_metadata_respond(dec, + FLAC__METADATA_TYPE_VORBIS_COMMENT); + s &= FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb); + s &= FLAC__seekable_stream_decoder_set_client_data(dec, data); + if (!s || (FLAC__seekable_stream_decoder_init(dec) != + FLAC__SEEKABLE_STREAM_DECODER_OK)) + return 0; + return 1; +} +#else /* FLAC_API_VERSION_CURRENT >= 7 */ +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: + case FLAC__STREAM_DECODER_READ_METADATA: + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + case FLAC__STREAM_DECODER_READ_FRAME: + case FLAC__STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__STREAM_DECODER_OGG_ERROR: + str = "error in the Ogg layer"; + break; + case FLAC__STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__STREAM_DECODER_ABORTED: + str = "decoder aborted by read"; + break; + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + ERROR("flac %s\n", str); +} +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static void flacMetadata(mpd_unused const flac_decoder * dec, + const FLAC__StreamMetadata * block, void *vdata) +{ + flac_metadata_common_cb(block, (FlacData *) vdata); +} + +static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, + const FLAC__Frame * frame, + const FLAC__int32 * const buf[], + void *vdata) +{ + FLAC__uint32 samples = frame->header.blocksize; + FlacData *data = (FlacData *) vdata; + float timeChange; + FLAC__uint64 newPosition = 0; + + timeChange = ((float)samples) / frame->header.sample_rate; + data->time += timeChange; + + flac_get_decode_position(dec, &newPosition); + if (data->position && newPosition >= data->position) { + assert(timeChange >= 0); + + data->bitRate = + ((newPosition - data->position) * 8.0 / timeChange) + / 1000 + 0.5; + } + data->position = newPosition; + + return flac_common_write(data, frame, buf); +} + +static struct tag *flacMetadataDup(char *file, int *vorbisCommentFound) +{ + struct tag *ret = NULL; + FLAC__Metadata_SimpleIterator *it; + FLAC__StreamMetadata *block = NULL; + + *vorbisCommentFound = 0; + + it = FLAC__metadata_simple_iterator_new(); + if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { + const char *err; + FLAC_API FLAC__Metadata_SimpleIteratorStatus s; + + s = FLAC__metadata_simple_iterator_status(it); + + switch (s) { /* slightly more human-friendly messages: */ + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: + err = "illegal input"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: + err = "error opening file"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: + err = "not a FLAC file"; + break; + default: + err = FLAC__Metadata_SimpleIteratorStatusString[s]; + } + DEBUG("flacMetadataDup: Reading '%s' " + "metadata gave the following error: %s\n", + file, err); + FLAC__metadata_simple_iterator_delete(it); + return ret; + } + + do { + block = FLAC__metadata_simple_iterator_get_block(it); + if (!block) + break; + if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { + ret = copyVorbisCommentBlockToMpdTag(block, ret); + + if (ret) + *vorbisCommentFound = 1; + } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { + if (!ret) + ret = tag_new(); + ret->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + } + FLAC__metadata_object_delete(block); + } while (FLAC__metadata_simple_iterator_next(it)); + + FLAC__metadata_simple_iterator_delete(it); + return ret; +} + +static struct tag *flacTagDup(char *file) +{ + struct tag *ret = NULL; + int foundVorbisComment = 0; + + ret = flacMetadataDup(file, &foundVorbisComment); + if (!ret) { + DEBUG("flacTagDup: Failed to grab information from: %s\n", + file); + return NULL; + } + if (!foundVorbisComment) { + struct tag *temp = tag_id3_load(file); + if (temp) { + temp->time = ret->time; + tag_free(ret); + ret = temp; + } + } + + return ret; +} + +static int flac_decode_internal(struct decoder * decoder, + InputStream * inStream, int is_ogg) +{ + flac_decoder *flacDec; + FlacData data; + const char *err = NULL; + + if (!(flacDec = flac_new())) + return -1; + init_FlacData(&data, decoder, inStream); + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + if(!FLAC__stream_decoder_set_metadata_respond(flacDec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + { + DEBUG(__FILE__": Failed to set metadata respond\n"); + } +#endif + + + if (is_ogg) { + if (!flac_ogg_init(flacDec, flacRead, flacSeek, flacTell, + flacLength, flacEOF, flacWrite, flacMetadata, + flacError, (void *)&data)) { + err = "doing Ogg init()"; + goto fail; + } + } else { + if (!flac_init(flacDec, flacRead, flacSeek, flacTell, + flacLength, flacEOF, flacWrite, flacMetadata, + flacError, (void *)&data)) { + err = "doing init()"; + goto fail; + } + if (!flac_process_metadata(flacDec)) { + err = "problem reading metadata"; + goto fail; + } + } + + decoder_initialized(decoder, &data.audio_format, data.total_time); + + while (1) { + if (!flac_process_single(flacDec)) + break; + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + FLAC__uint64 sampleToSeek = decoder_seek_where(decoder) * + data.audio_format.sample_rate + 0.5; + if (flac_seek_absolute(flacDec, sampleToSeek)) { + decoder_clear(decoder); + data.time = ((float)sampleToSeek) / + data.audio_format.sample_rate; + data.position = 0; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else if (flac_get_state(flacDec) == flac_decoder_eof) + break; + } + if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) { + flacPrintErroredState(flac_get_state(flacDec)); + flac_finish(flacDec); + } + +fail: + if (data.replayGainInfo) + freeReplayGainInfo(data.replayGainInfo); + + if (flacDec) + flac_delete(flacDec); + + if (err) { + ERROR("flac %s\n", err); + return -1; + } + return 0; +} + +static int flac_decode(struct decoder * decoder, InputStream * inStream) +{ + return flac_decode_internal(decoder, inStream, 0); +} + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 && \ + !defined(HAVE_OGGFLAC) +static struct tag *oggflac_tag_dup(char *file) +{ + struct tag *ret = NULL; + FLAC__Metadata_Iterator *it; + FLAC__StreamMetadata *block; + FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); + + if (!(FLAC__metadata_chain_read_ogg(chain, file))) + goto out; + it = FLAC__metadata_iterator_new(); + FLAC__metadata_iterator_init(it, chain); + do { + if (!(block = FLAC__metadata_iterator_get_block(it))) + break; + if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { + ret = copyVorbisCommentBlockToMpdTag(block, ret); + } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { + if (!ret) + ret = tag_new(); + ret->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + } + } while (FLAC__metadata_iterator_next(it)); + FLAC__metadata_iterator_delete(it); +out: + FLAC__metadata_chain_delete(chain); + return ret; +} + +static int oggflac_decode(struct decoder *decoder, InputStream * inStream) +{ + return flac_decode_internal(decoder, inStream, 1); +} + +static bool oggflac_try_decode(InputStream * inStream) +{ + return FLAC_API_SUPPORTS_OGG_FLAC && + ogg_stream_type_detect(inStream) == FLAC; +} + +static const char *oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *oggflac_mime_types[] = { "audio/x-flac+ogg", + "application/ogg", + "application/x-ogg", + NULL }; + +struct decoder_plugin oggflacPlugin = { + .name = "oggflac", + .try_decode = oggflac_try_decode, + .stream_decode = oggflac_decode, + .tag_dup = oggflac_tag_dup, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = oggflac_suffixes, + .mime_types = oggflac_mime_types +}; + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static const char *flacSuffixes[] = { "flac", NULL }; +static const char *flac_mime_types[] = { "audio/x-flac", + "application/x-flac", + NULL }; + +struct decoder_plugin flacPlugin = { + .name = "flac", + .stream_decode = flac_decode, + .tag_dup = flacTagDup, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = flacSuffixes, + .mime_types = flac_mime_types +}; |