From 9b9abff97272b52f133ff23addd58b6a90a49a73 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 4 Jan 2010 21:36:33 +0100 Subject: renamed decoder plugin sources Make it X_decoder_plugin.c. --- Makefile.am | 30 +- src/decoder/audiofile_decoder_plugin.c | 256 +++++++ src/decoder/audiofile_plugin.c | 256 ------- src/decoder/faad_decoder_plugin.c | 515 +++++++++++++ src/decoder/faad_plugin.c | 515 ------------- src/decoder/ffmpeg_decoder_plugin.c | 552 ++++++++++++++ src/decoder/ffmpeg_plugin.c | 552 -------------- src/decoder/flac_decoder_plugin.c | 838 +++++++++++++++++++++ src/decoder/flac_plugin.c | 838 --------------------- src/decoder/fluidsynth_decoder_plugin.c | 244 ++++++ src/decoder/fluidsynth_plugin.c | 244 ------ src/decoder/mad_decoder_plugin.c | 1235 +++++++++++++++++++++++++++++++ src/decoder/mad_plugin.c | 1235 ------------------------------- src/decoder/mikmod_decoder_plugin.c | 237 ++++++ src/decoder/mikmod_plugin.c | 237 ------ src/decoder/modplug_decoder_plugin.c | 194 +++++ src/decoder/modplug_plugin.c | 194 ----- src/decoder/mp4ff_decoder_plugin.c | 421 +++++++++++ src/decoder/mp4ff_plugin.c | 421 ----------- src/decoder/mpcdec_decoder_plugin.c | 339 +++++++++ src/decoder/mpcdec_plugin.c | 339 --------- src/decoder/oggflac_decoder_plugin.c | 354 +++++++++ src/decoder/oggflac_plugin.c | 354 --------- src/decoder/sidplay_decoder_plugin.cxx | 417 +++++++++++ src/decoder/sidplay_plugin.cxx | 417 ----------- src/decoder/vorbis_decoder_plugin.c | 434 +++++++++++ src/decoder/vorbis_plugin.c | 434 ----------- src/decoder/wavpack_decoder_plugin.c | 603 +++++++++++++++ src/decoder/wavpack_plugin.c | 603 --------------- src/decoder/wildmidi_decoder_plugin.c | 147 ++++ src/decoder/wildmidi_plugin.c | 147 ---- 31 files changed, 6801 insertions(+), 6801 deletions(-) create mode 100644 src/decoder/audiofile_decoder_plugin.c delete mode 100644 src/decoder/audiofile_plugin.c create mode 100644 src/decoder/faad_decoder_plugin.c delete mode 100644 src/decoder/faad_plugin.c create mode 100644 src/decoder/ffmpeg_decoder_plugin.c delete mode 100644 src/decoder/ffmpeg_plugin.c create mode 100644 src/decoder/flac_decoder_plugin.c delete mode 100644 src/decoder/flac_plugin.c create mode 100644 src/decoder/fluidsynth_decoder_plugin.c delete mode 100644 src/decoder/fluidsynth_plugin.c create mode 100644 src/decoder/mad_decoder_plugin.c delete mode 100644 src/decoder/mad_plugin.c create mode 100644 src/decoder/mikmod_decoder_plugin.c delete mode 100644 src/decoder/mikmod_plugin.c create mode 100644 src/decoder/modplug_decoder_plugin.c delete mode 100644 src/decoder/modplug_plugin.c create mode 100644 src/decoder/mp4ff_decoder_plugin.c delete mode 100644 src/decoder/mp4ff_plugin.c create mode 100644 src/decoder/mpcdec_decoder_plugin.c delete mode 100644 src/decoder/mpcdec_plugin.c create mode 100644 src/decoder/oggflac_decoder_plugin.c delete mode 100644 src/decoder/oggflac_plugin.c create mode 100644 src/decoder/sidplay_decoder_plugin.cxx delete mode 100644 src/decoder/sidplay_plugin.cxx create mode 100644 src/decoder/vorbis_decoder_plugin.c delete mode 100644 src/decoder/vorbis_plugin.c create mode 100644 src/decoder/wavpack_decoder_plugin.c delete mode 100644 src/decoder/wavpack_plugin.c create mode 100644 src/decoder/wildmidi_decoder_plugin.c delete mode 100644 src/decoder/wildmidi_plugin.c diff --git a/Makefile.am b/Makefile.am index 5394e7932..24035b458 100644 --- a/Makefile.am +++ b/Makefile.am @@ -434,7 +434,7 @@ DECODER_SRC = \ src/decoder_list.c if HAVE_MAD -DECODER_SRC += src/decoder/mad_plugin.c +DECODER_SRC += src/decoder/mad_decoder_plugin.c endif if HAVE_MPG123 @@ -442,19 +442,19 @@ DECODER_SRC += src/decoder/mpg123_decoder_plugin.c endif if HAVE_MPCDEC -DECODER_SRC += src/decoder/mpcdec_plugin.c +DECODER_SRC += src/decoder/mpcdec_decoder_plugin.c endif if HAVE_WAVPACK -DECODER_SRC += src/decoder/wavpack_plugin.c +DECODER_SRC += src/decoder/wavpack_decoder_plugin.c endif if HAVE_FAAD -DECODER_SRC += src/decoder/faad_plugin.c +DECODER_SRC += src/decoder/faad_decoder_plugin.c endif if HAVE_MP4 -DECODER_SRC += src/decoder/mp4ff_plugin.c +DECODER_SRC += src/decoder/mp4ff_decoder_plugin.c endif if HAVE_OGG_COMMON @@ -469,43 +469,43 @@ DECODER_SRC += \ endif if ENABLE_VORBIS_DECODER -DECODER_SRC += src/decoder/vorbis_plugin.c +DECODER_SRC += src/decoder/vorbis_decoder_plugin.c endif if HAVE_FLAC -DECODER_SRC += src/decoder/flac_plugin.c +DECODER_SRC += src/decoder/flac_decoder_plugin.c endif if HAVE_OGGFLAC -DECODER_SRC += src/decoder/oggflac_plugin.c +DECODER_SRC += src/decoder/oggflac_decoder_plugin.c endif if HAVE_AUDIOFILE -DECODER_SRC += src/decoder/audiofile_plugin.c +DECODER_SRC += src/decoder/audiofile_decoder_plugin.c endif if ENABLE_MIKMOD_DECODER -DECODER_SRC += src/decoder/mikmod_plugin.c +DECODER_SRC += src/decoder/mikmod_decoder_plugin.c endif if HAVE_MODPLUG -DECODER_SRC += src/decoder/modplug_plugin.c +DECODER_SRC += src/decoder/modplug_decoder_plugin.c endif if ENABLE_SIDPLAY -DECODER_SRC += src/decoder/sidplay_plugin.cxx +DECODER_SRC += src/decoder/sidplay_decoder_plugin.cxx endif if ENABLE_FLUIDSYNTH -DECODER_SRC += src/decoder/fluidsynth_plugin.c +DECODER_SRC += src/decoder/fluidsynth_decoder_plugin.c endif if ENABLE_WILDMIDI -DECODER_SRC += src/decoder/wildmidi_plugin.c +DECODER_SRC += src/decoder/wildmidi_decoder_plugin.c endif if HAVE_FFMPEG -DECODER_SRC += src/decoder/ffmpeg_plugin.c +DECODER_SRC += src/decoder/ffmpeg_decoder_plugin.c endif if ENABLE_SNDFILE diff --git a/src/decoder/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c new file mode 100644 index 000000000..3026f3cc7 --- /dev/null +++ b/src/decoder/audiofile_decoder_plugin.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" + +#include +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "audiofile" + +/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ +#define CHUNK_SIZE 1020 + +static int audiofile_get_duration(const char *file) +{ + int total_time; + AFfilehandle af_fp = afOpenFile(file, "r", NULL); + if (af_fp == AF_NULL_FILEHANDLE) { + return -1; + } + total_time = (int) + ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) + / afGetRate(af_fp, AF_DEFAULT_TRACK)); + afCloseFile(af_fp); + return total_time; +} + +static ssize_t +audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + GError *error = NULL; + size_t nbytes; + + nbytes = input_stream_read(is, data, length, &error); + if (nbytes == 0 && error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + return -1; + } + + return nbytes; +} + +static long +audiofile_file_length(AFvirtualfile *vfile) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + return is->size; +} + +static long +audiofile_file_tell(AFvirtualfile *vfile) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + return is->offset; +} + +static void +audiofile_file_destroy(AFvirtualfile *vfile) +{ + assert(vfile->closure != NULL); + + vfile->closure = NULL; +} + +static long +audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative) +{ + struct input_stream *is = (struct input_stream *) vfile->closure; + int whence = (is_relative ? SEEK_CUR : SEEK_SET); + if (input_stream_seek(is, offset, whence, NULL)) { + return is->offset; + } else { + return -1; + } +} + +static AFvirtualfile * +setup_virtual_fops(struct input_stream *stream) +{ + AFvirtualfile *vf = g_malloc(sizeof(AFvirtualfile)); + vf->closure = stream; + vf->write = NULL; + vf->read = audiofile_file_read; + vf->length = audiofile_file_length; + vf->destroy = audiofile_file_destroy; + vf->seek = audiofile_file_seek; + vf->tell = audiofile_file_tell; + return vf; +} + +static enum sample_format +audiofile_bits_to_sample_format(int bits) +{ + switch (bits) { + case 8: + return SAMPLE_FORMAT_S8; + + case 16: + return SAMPLE_FORMAT_S16; + + case 24: + return SAMPLE_FORMAT_S24_P32; + + case 32: + return SAMPLE_FORMAT_S32; + } + + return SAMPLE_FORMAT_UNDEFINED; +} + +static enum sample_format +audiofile_setup_sample_format(AFfilehandle af_fp) +{ + int fs, bits; + + afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) { + g_debug("input file has %d bit samples, converting to 16", + bits); + bits = 16; + } + + afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, + AF_SAMPFMT_TWOSCOMP, bits); + afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + + return audiofile_bits_to_sample_format(bits); +} + +static void +audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + GError *error = NULL; + AFvirtualfile *vf; + int fs, frame_count; + AFfilehandle af_fp; + struct audio_format audio_format; + float total_time; + uint16_t bit_rate; + int ret; + char chunk[CHUNK_SIZE]; + enum decoder_command cmd; + + if (!is->seekable) { + g_warning("not seekable"); + return; + } + + vf = setup_virtual_fops(is); + + af_fp = afOpenVirtualFile(vf, "r", NULL); + if (af_fp == AF_NULL_FILEHANDLE) { + g_warning("failed to input stream\n"); + return; + } + + if (!audio_format_init_checked(&audio_format, + afGetRate(af_fp, AF_DEFAULT_TRACK), + audiofile_setup_sample_format(af_fp), + afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK), + &error)) { + g_warning("%s", error->message); + g_error_free(error); + afCloseFile(af_fp); + return; + } + + frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); + + total_time = ((float)frame_count / (float)audio_format.sample_rate); + + bit_rate = (uint16_t)(is->size * 8.0 / total_time / 1000.0 + 0.5); + + fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); + + decoder_initialized(decoder, &audio_format, true, total_time); + + do { + ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, + CHUNK_SIZE / fs); + if (ret <= 0) + break; + + cmd = decoder_data(decoder, NULL, + chunk, ret * fs, + bit_rate); + + if (cmd == DECODE_COMMAND_SEEK) { + AFframecount frame = decoder_seek_where(decoder) * + audio_format.sample_rate; + afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); + + decoder_command_finished(decoder); + cmd = DECODE_COMMAND_NONE; + } + } while (cmd == DECODE_COMMAND_NONE); + + afCloseFile(af_fp); +} + +static struct tag *audiofile_tag_dup(const char *file) +{ + struct tag *ret = NULL; + int total_time = audiofile_get_duration(file); + + if (total_time >= 0) { + ret = tag_new(); + ret->time = total_time; + } else { + g_debug("Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static const char *const audiofile_suffixes[] = { + "wav", "au", "aiff", "aif", NULL +}; + +static const char *const audiofile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + NULL +}; + +const struct decoder_plugin audiofile_decoder_plugin = { + .name = "audiofile", + .stream_decode = audiofile_stream_decode, + .tag_dup = audiofile_tag_dup, + .suffixes = audiofile_suffixes, + .mime_types = audiofile_mime_types, +}; diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_plugin.c deleted file mode 100644 index 3026f3cc7..000000000 --- a/src/decoder/audiofile_plugin.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" - -#include -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "audiofile" - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -static int audiofile_get_duration(const char *file) -{ - int total_time; - AFfilehandle af_fp = afOpenFile(file, "r", NULL); - if (af_fp == AF_NULL_FILEHANDLE) { - return -1; - } - total_time = (int) - ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) - / afGetRate(af_fp, AF_DEFAULT_TRACK)); - afCloseFile(af_fp); - return total_time; -} - -static ssize_t -audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - GError *error = NULL; - size_t nbytes; - - nbytes = input_stream_read(is, data, length, &error); - if (nbytes == 0 && error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - return -1; - } - - return nbytes; -} - -static long -audiofile_file_length(AFvirtualfile *vfile) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - return is->size; -} - -static long -audiofile_file_tell(AFvirtualfile *vfile) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - return is->offset; -} - -static void -audiofile_file_destroy(AFvirtualfile *vfile) -{ - assert(vfile->closure != NULL); - - vfile->closure = NULL; -} - -static long -audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative) -{ - struct input_stream *is = (struct input_stream *) vfile->closure; - int whence = (is_relative ? SEEK_CUR : SEEK_SET); - if (input_stream_seek(is, offset, whence, NULL)) { - return is->offset; - } else { - return -1; - } -} - -static AFvirtualfile * -setup_virtual_fops(struct input_stream *stream) -{ - AFvirtualfile *vf = g_malloc(sizeof(AFvirtualfile)); - vf->closure = stream; - vf->write = NULL; - vf->read = audiofile_file_read; - vf->length = audiofile_file_length; - vf->destroy = audiofile_file_destroy; - vf->seek = audiofile_file_seek; - vf->tell = audiofile_file_tell; - return vf; -} - -static enum sample_format -audiofile_bits_to_sample_format(int bits) -{ - switch (bits) { - case 8: - return SAMPLE_FORMAT_S8; - - case 16: - return SAMPLE_FORMAT_S16; - - case 24: - return SAMPLE_FORMAT_S24_P32; - - case 32: - return SAMPLE_FORMAT_S32; - } - - return SAMPLE_FORMAT_UNDEFINED; -} - -static enum sample_format -audiofile_setup_sample_format(AFfilehandle af_fp) -{ - int fs, bits; - - afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) { - g_debug("input file has %d bit samples, converting to 16", - bits); - bits = 16; - } - - afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, - AF_SAMPFMT_TWOSCOMP, bits); - afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - - return audiofile_bits_to_sample_format(bits); -} - -static void -audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) -{ - GError *error = NULL; - AFvirtualfile *vf; - int fs, frame_count; - AFfilehandle af_fp; - struct audio_format audio_format; - float total_time; - uint16_t bit_rate; - int ret; - char chunk[CHUNK_SIZE]; - enum decoder_command cmd; - - if (!is->seekable) { - g_warning("not seekable"); - return; - } - - vf = setup_virtual_fops(is); - - af_fp = afOpenVirtualFile(vf, "r", NULL); - if (af_fp == AF_NULL_FILEHANDLE) { - g_warning("failed to input stream\n"); - return; - } - - if (!audio_format_init_checked(&audio_format, - afGetRate(af_fp, AF_DEFAULT_TRACK), - audiofile_setup_sample_format(af_fp), - afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK), - &error)) { - g_warning("%s", error->message); - g_error_free(error); - afCloseFile(af_fp); - return; - } - - frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); - - total_time = ((float)frame_count / (float)audio_format.sample_rate); - - bit_rate = (uint16_t)(is->size * 8.0 / total_time / 1000.0 + 0.5); - - fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); - - decoder_initialized(decoder, &audio_format, true, total_time); - - do { - ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, - CHUNK_SIZE / fs); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, NULL, - chunk, ret * fs, - bit_rate); - - if (cmd == DECODE_COMMAND_SEEK) { - AFframecount frame = decoder_seek_where(decoder) * - audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); - - decoder_command_finished(decoder); - cmd = DECODE_COMMAND_NONE; - } - } while (cmd == DECODE_COMMAND_NONE); - - afCloseFile(af_fp); -} - -static struct tag *audiofile_tag_dup(const char *file) -{ - struct tag *ret = NULL; - int total_time = audiofile_get_duration(file); - - if (total_time >= 0) { - ret = tag_new(); - ret->time = total_time; - } else { - g_debug("Failed to get total song time from: %s\n", - file); - } - - return ret; -} - -static const char *const audiofile_suffixes[] = { - "wav", "au", "aiff", "aif", NULL -}; - -static const char *const audiofile_mime_types[] = { - "audio/x-wav", - "audio/x-aiff", - NULL -}; - -const struct decoder_plugin audiofile_decoder_plugin = { - .name = "audiofile", - .stream_decode = audiofile_stream_decode, - .tag_dup = audiofile_tag_dup, - .suffixes = audiofile_suffixes, - .mime_types = audiofile_mime_types, -}; diff --git a/src/decoder/faad_decoder_plugin.c b/src/decoder/faad_decoder_plugin.c new file mode 100644 index 000000000..8f932ad58 --- /dev/null +++ b/src/decoder/faad_decoder_plugin.c @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "decoder_buffer.h" +#include "audio_check.h" + +#define AAC_MAX_CHANNELS 6 + +#include +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "faad" + +static const unsigned adts_sample_rates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +/** + * The GLib quark used for errors reported by this plugin. + */ +static inline GQuark +faad_decoder_quark(void) +{ + return g_quark_from_static_string("faad"); +} + +/** + * Check whether the buffer head is an AAC frame, and return the frame + * length. Returns 0 if it is not a frame. + */ +static size_t +adts_check_frame(const unsigned char *data) +{ + /* check syncword */ + if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0))) + return 0; + + return (((unsigned int)data[3] & 0x3) << 11) | + (((unsigned int)data[4]) << 3) | + (data[5] >> 5); +} + +/** + * Find the next AAC frame in the buffer. Returns 0 if no frame is + * found or if not enough data is available. + */ +static size_t +adts_find_frame(struct decoder_buffer *buffer) +{ + const unsigned char *data, *p; + size_t length, frame_length; + bool ret; + + while (true) { + data = decoder_buffer_read(buffer, &length); + if (data == NULL || length < 8) { + /* not enough data yet */ + ret = decoder_buffer_fill(buffer); + if (!ret) + /* failed */ + return 0; + + continue; + } + + /* find the 0xff marker */ + p = memchr(data, 0xff, length); + if (p == NULL) { + /* no marker - discard the buffer */ + decoder_buffer_consume(buffer, length); + continue; + } + + if (p > data) { + /* discard data before 0xff */ + decoder_buffer_consume(buffer, p - data); + continue; + } + + /* is it a frame? */ + frame_length = adts_check_frame(data); + if (frame_length == 0) { + /* it's just some random 0xff byte; discard it + and continue searching */ + decoder_buffer_consume(buffer, 1); + continue; + } + + if (length < frame_length) { + /* available buffer size is smaller than the + frame will be - attempt to read more + data */ + ret = decoder_buffer_fill(buffer); + if (!ret) { + /* not enough data; discard this frame + to prevent a possible buffer + overflow */ + data = decoder_buffer_read(buffer, &length); + if (data != NULL) + decoder_buffer_consume(buffer, length); + } + + continue; + } + + /* found a full frame! */ + return frame_length; + } +} + +static float +adts_song_duration(struct decoder_buffer *buffer) +{ + unsigned int frames, frame_length; + unsigned sample_rate = 0; + float frames_per_second; + + /* Read all frames to ensure correct time and bitrate */ + for (frames = 0;; frames++) { + frame_length = adts_find_frame(buffer); + if (frame_length == 0) + break; + + + if (frames == 0) { + const unsigned char *data; + size_t buffer_length; + + data = decoder_buffer_read(buffer, &buffer_length); + assert(data != NULL); + assert(frame_length <= buffer_length); + + sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; + } + + decoder_buffer_consume(buffer, frame_length); + } + + frames_per_second = (float)sample_rate / 1024.0; + if (frames_per_second <= 0) + return -1; + + return (float)frames / frames_per_second; +} + +static float +faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) +{ + size_t fileread; + size_t tagsize; + const unsigned char *data; + size_t length; + bool success; + + fileread = is->size >= 0 ? is->size : 0; + + decoder_buffer_fill(buffer); + data = decoder_buffer_read(buffer, &length); + if (data == NULL) + return -1; + + tagsize = 0; + if (length >= 10 && !memcmp(data, "ID3", 3)) { + /* skip the ID3 tag */ + + tagsize = (data[6] << 21) | (data[7] << 14) | + (data[8] << 7) | (data[9] << 0); + + tagsize += 10; + + success = decoder_buffer_skip(buffer, tagsize) && + decoder_buffer_fill(buffer); + if (!success) + return -1; + + data = decoder_buffer_read(buffer, &length); + if (data == NULL) + return -1; + } + + if (is->seekable && length >= 2 && + data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) { + /* obtain the duration from the ADTS header */ + float song_length = adts_song_duration(buffer); + + input_stream_seek(is, tagsize, SEEK_SET, NULL); + + data = decoder_buffer_read(buffer, &length); + if (data != NULL) + decoder_buffer_consume(buffer, length); + decoder_buffer_fill(buffer); + + return song_length; + } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { + /* obtain the duration from the ADIF header */ + unsigned bit_rate; + size_t skip_size = (data[4] & 0x80) ? 9 : 0; + + if (8 + skip_size > length) + /* not enough data yet; skip parsing this + header */ + return -1; + + bit_rate = ((data[4 + skip_size] & 0x0F) << 19) | + (data[5 + skip_size] << 11) | + (data[6 + skip_size] << 3) | + (data[7 + skip_size] & 0xE0); + + if (fileread != 0 && bit_rate != 0) + return fileread * 8.0 / bit_rate; + else + return fileread; + } else + return -1; +} + +/** + * Wrapper for faacDecInit() which works around some API + * inconsistencies in libfaad. + */ +static bool +faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, + struct audio_format *audio_format, GError **error_r) +{ + union { + /* deconst hack for libfaad */ + const void *in; + void *out; + } u; + size_t length; + int32_t nbytes; + uint32_t sample_rate; + uint8_t channels; +#ifdef HAVE_FAAD_LONG + /* neaacdec.h declares all arguments as "unsigned long", but + internally expects uint32_t pointers. To avoid gcc + warnings, use this workaround. */ + unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate; +#else + uint32_t *sample_rate_p = &sample_rate; +#endif + + u.in = decoder_buffer_read(buffer, &length); + if (u.in == NULL) { + g_set_error(error_r, faad_decoder_quark(), 0, + "Empty file"); + return false; + } + + nbytes = faacDecInit(decoder, u.out, +#ifdef HAVE_FAAD_BUFLEN_FUNCS + length, +#endif + sample_rate_p, &channels); + if (nbytes < 0) { + g_set_error(error_r, faad_decoder_quark(), 0, + "Not an AAC stream"); + return false; + } + + decoder_buffer_consume(buffer, nbytes); + + return audio_format_init_checked(audio_format, sample_rate, + SAMPLE_FORMAT_S16, channels, error_r); +} + +/** + * Wrapper for faacDecDecode() which works around some API + * inconsistencies in libfaad. + */ +static const void * +faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer, + faacDecFrameInfo *frame_info) +{ + union { + /* deconst hack for libfaad */ + const void *in; + void *out; + } u; + size_t length; + void *result; + + u.in = decoder_buffer_read(buffer, &length); + if (u.in == NULL) + return NULL; + + result = faacDecDecode(decoder, frame_info, + u.out +#ifdef HAVE_FAAD_BUFLEN_FUNCS + , length +#endif + ); + + return result; +} + +/** + * Get a song file's total playing time in seconds, as a float. + * Returns 0 if the duration is unknown, and a negative value if the + * file is invalid. + */ +static float +faad_get_file_time_float(struct input_stream *is) +{ + struct decoder_buffer *buffer; + float length; + faacDecHandle decoder; + faacDecConfigurationPtr config; + + buffer = decoder_buffer_new(NULL, is, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + length = faad_song_duration(buffer, is); + + if (length < 0) { + bool ret; + struct audio_format audio_format; + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + faacDecSetConfiguration(decoder, config); + + decoder_buffer_fill(buffer); + + ret = faad_decoder_init(decoder, buffer, &audio_format, NULL); + if (ret) + length = 0; + + faacDecClose(decoder); + } + + decoder_buffer_free(buffer); + + return length; +} + +/** + * Get a song file's total playing time in seconds, as an int. + * Returns 0 if the duration is unknown, and a negative value if the + * file is invalid. + */ +static int +faad_get_file_time(struct input_stream *is) +{ + int file_time = -1; + float length; + + if ((length = faad_get_file_time_float(is)) >= 0) + file_time = length + 0.5; + + return file_time; +} + +static void +faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) +{ + GError *error = NULL; + float total_time = 0; + faacDecHandle decoder; + struct audio_format audio_format; + faacDecConfigurationPtr config; + bool ret; + uint16_t bit_rate = 0; + struct decoder_buffer *buffer; + enum decoder_command cmd; + + buffer = decoder_buffer_new(mpd_decoder, is, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + total_time = faad_song_duration(buffer, is); + + /* create the libfaad decoder */ + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; +#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX + config->downMatrix = 1; +#endif +#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR + config->dontUpSampleImplicitSBR = 0; +#endif + faacDecSetConfiguration(decoder, config); + + while (!decoder_buffer_is_full(buffer) && + !input_stream_eof(is) && + decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { + adts_find_frame(buffer); + decoder_buffer_fill(buffer); + } + + /* initialize it */ + + ret = faad_decoder_init(decoder, buffer, &audio_format, &error); + if (!ret) { + g_warning("%s", error->message); + g_error_free(error); + faacDecClose(decoder); + return; + } + + /* initialize the MPD core */ + + decoder_initialized(mpd_decoder, &audio_format, false, total_time); + + /* the decoder loop */ + + do { + size_t frame_size; + const void *decoded; + faacDecFrameInfo frame_info; + + /* find the next frame */ + + frame_size = adts_find_frame(buffer); + if (frame_size == 0) + /* end of file */ + break; + + /* decode it */ + + decoded = faad_decoder_decode(decoder, buffer, &frame_info); + + if (frame_info.error > 0) { + g_warning("error decoding AAC stream: %s\n", + faacDecGetErrorMessage(frame_info.error)); + break; + } + + if (frame_info.channels != audio_format.channels) { + g_warning("channel count changed from %u to %u", + audio_format.channels, frame_info.channels); + break; + } + +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + if (frame_info.samplerate != audio_format.sample_rate) { + g_warning("sample rate changed from %u to %lu", + audio_format.sample_rate, + (unsigned long)frame_info.samplerate); + break; + } +#endif + + decoder_buffer_consume(buffer, frame_info.bytesconsumed); + + /* update bit rate and position */ + + if (frame_info.samples > 0) { + bit_rate = frame_info.bytesconsumed * 8.0 * + frame_info.channels * audio_format.sample_rate / + frame_info.samples / 1000 + 0.5; + } + + /* send PCM samples to MPD */ + + cmd = decoder_data(mpd_decoder, is, decoded, + (size_t)frame_info.samples * 2, + bit_rate); + } while (cmd != DECODE_COMMAND_STOP); + + /* cleanup */ + + faacDecClose(decoder); +} + +static struct tag * +faad_stream_tag(struct input_stream *is) +{ + int file_time = faad_get_file_time(is); + struct tag *tag; + + if (file_time < 0) + return NULL; + + tag = tag_new(); + tag->time = file_time; + return tag; +} + +static const char *const faad_suffixes[] = { "aac", NULL }; +static const char *const faad_mime_types[] = { + "audio/aac", "audio/aacp", NULL +}; + +const struct decoder_plugin faad_decoder_plugin = { + .name = "faad", + .stream_decode = faad_stream_decode, + .stream_tag = faad_stream_tag, + .suffixes = faad_suffixes, + .mime_types = faad_mime_types, +}; diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_plugin.c deleted file mode 100644 index 8f932ad58..000000000 --- a/src/decoder/faad_plugin.c +++ /dev/null @@ -1,515 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "decoder_buffer.h" -#include "audio_check.h" - -#define AAC_MAX_CHANNELS 6 - -#include -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "faad" - -static const unsigned adts_sample_rates[] = - { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, 7350, 0, 0, 0 -}; - -/** - * The GLib quark used for errors reported by this plugin. - */ -static inline GQuark -faad_decoder_quark(void) -{ - return g_quark_from_static_string("faad"); -} - -/** - * Check whether the buffer head is an AAC frame, and return the frame - * length. Returns 0 if it is not a frame. - */ -static size_t -adts_check_frame(const unsigned char *data) -{ - /* check syncword */ - if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0))) - return 0; - - return (((unsigned int)data[3] & 0x3) << 11) | - (((unsigned int)data[4]) << 3) | - (data[5] >> 5); -} - -/** - * Find the next AAC frame in the buffer. Returns 0 if no frame is - * found or if not enough data is available. - */ -static size_t -adts_find_frame(struct decoder_buffer *buffer) -{ - const unsigned char *data, *p; - size_t length, frame_length; - bool ret; - - while (true) { - data = decoder_buffer_read(buffer, &length); - if (data == NULL || length < 8) { - /* not enough data yet */ - ret = decoder_buffer_fill(buffer); - if (!ret) - /* failed */ - return 0; - - continue; - } - - /* find the 0xff marker */ - p = memchr(data, 0xff, length); - if (p == NULL) { - /* no marker - discard the buffer */ - decoder_buffer_consume(buffer, length); - continue; - } - - if (p > data) { - /* discard data before 0xff */ - decoder_buffer_consume(buffer, p - data); - continue; - } - - /* is it a frame? */ - frame_length = adts_check_frame(data); - if (frame_length == 0) { - /* it's just some random 0xff byte; discard it - and continue searching */ - decoder_buffer_consume(buffer, 1); - continue; - } - - if (length < frame_length) { - /* available buffer size is smaller than the - frame will be - attempt to read more - data */ - ret = decoder_buffer_fill(buffer); - if (!ret) { - /* not enough data; discard this frame - to prevent a possible buffer - overflow */ - data = decoder_buffer_read(buffer, &length); - if (data != NULL) - decoder_buffer_consume(buffer, length); - } - - continue; - } - - /* found a full frame! */ - return frame_length; - } -} - -static float -adts_song_duration(struct decoder_buffer *buffer) -{ - unsigned int frames, frame_length; - unsigned sample_rate = 0; - float frames_per_second; - - /* Read all frames to ensure correct time and bitrate */ - for (frames = 0;; frames++) { - frame_length = adts_find_frame(buffer); - if (frame_length == 0) - break; - - - if (frames == 0) { - const unsigned char *data; - size_t buffer_length; - - data = decoder_buffer_read(buffer, &buffer_length); - assert(data != NULL); - assert(frame_length <= buffer_length); - - sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; - } - - decoder_buffer_consume(buffer, frame_length); - } - - frames_per_second = (float)sample_rate / 1024.0; - if (frames_per_second <= 0) - return -1; - - return (float)frames / frames_per_second; -} - -static float -faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) -{ - size_t fileread; - size_t tagsize; - const unsigned char *data; - size_t length; - bool success; - - fileread = is->size >= 0 ? is->size : 0; - - decoder_buffer_fill(buffer); - data = decoder_buffer_read(buffer, &length); - if (data == NULL) - return -1; - - tagsize = 0; - if (length >= 10 && !memcmp(data, "ID3", 3)) { - /* skip the ID3 tag */ - - tagsize = (data[6] << 21) | (data[7] << 14) | - (data[8] << 7) | (data[9] << 0); - - tagsize += 10; - - success = decoder_buffer_skip(buffer, tagsize) && - decoder_buffer_fill(buffer); - if (!success) - return -1; - - data = decoder_buffer_read(buffer, &length); - if (data == NULL) - return -1; - } - - if (is->seekable && length >= 2 && - data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) { - /* obtain the duration from the ADTS header */ - float song_length = adts_song_duration(buffer); - - input_stream_seek(is, tagsize, SEEK_SET, NULL); - - data = decoder_buffer_read(buffer, &length); - if (data != NULL) - decoder_buffer_consume(buffer, length); - decoder_buffer_fill(buffer); - - return song_length; - } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { - /* obtain the duration from the ADIF header */ - unsigned bit_rate; - size_t skip_size = (data[4] & 0x80) ? 9 : 0; - - if (8 + skip_size > length) - /* not enough data yet; skip parsing this - header */ - return -1; - - bit_rate = ((data[4 + skip_size] & 0x0F) << 19) | - (data[5 + skip_size] << 11) | - (data[6 + skip_size] << 3) | - (data[7 + skip_size] & 0xE0); - - if (fileread != 0 && bit_rate != 0) - return fileread * 8.0 / bit_rate; - else - return fileread; - } else - return -1; -} - -/** - * Wrapper for faacDecInit() which works around some API - * inconsistencies in libfaad. - */ -static bool -faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, - struct audio_format *audio_format, GError **error_r) -{ - union { - /* deconst hack for libfaad */ - const void *in; - void *out; - } u; - size_t length; - int32_t nbytes; - uint32_t sample_rate; - uint8_t channels; -#ifdef HAVE_FAAD_LONG - /* neaacdec.h declares all arguments as "unsigned long", but - internally expects uint32_t pointers. To avoid gcc - warnings, use this workaround. */ - unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate; -#else - uint32_t *sample_rate_p = &sample_rate; -#endif - - u.in = decoder_buffer_read(buffer, &length); - if (u.in == NULL) { - g_set_error(error_r, faad_decoder_quark(), 0, - "Empty file"); - return false; - } - - nbytes = faacDecInit(decoder, u.out, -#ifdef HAVE_FAAD_BUFLEN_FUNCS - length, -#endif - sample_rate_p, &channels); - if (nbytes < 0) { - g_set_error(error_r, faad_decoder_quark(), 0, - "Not an AAC stream"); - return false; - } - - decoder_buffer_consume(buffer, nbytes); - - return audio_format_init_checked(audio_format, sample_rate, - SAMPLE_FORMAT_S16, channels, error_r); -} - -/** - * Wrapper for faacDecDecode() which works around some API - * inconsistencies in libfaad. - */ -static const void * -faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer, - faacDecFrameInfo *frame_info) -{ - union { - /* deconst hack for libfaad */ - const void *in; - void *out; - } u; - size_t length; - void *result; - - u.in = decoder_buffer_read(buffer, &length); - if (u.in == NULL) - return NULL; - - result = faacDecDecode(decoder, frame_info, - u.out -#ifdef HAVE_FAAD_BUFLEN_FUNCS - , length -#endif - ); - - return result; -} - -/** - * Get a song file's total playing time in seconds, as a float. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. - */ -static float -faad_get_file_time_float(struct input_stream *is) -{ - struct decoder_buffer *buffer; - float length; - faacDecHandle decoder; - faacDecConfigurationPtr config; - - buffer = decoder_buffer_new(NULL, is, - FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - length = faad_song_duration(buffer, is); - - if (length < 0) { - bool ret; - struct audio_format audio_format; - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; - faacDecSetConfiguration(decoder, config); - - decoder_buffer_fill(buffer); - - ret = faad_decoder_init(decoder, buffer, &audio_format, NULL); - if (ret) - length = 0; - - faacDecClose(decoder); - } - - decoder_buffer_free(buffer); - - return length; -} - -/** - * Get a song file's total playing time in seconds, as an int. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. - */ -static int -faad_get_file_time(struct input_stream *is) -{ - int file_time = -1; - float length; - - if ((length = faad_get_file_time_float(is)) >= 0) - file_time = length + 0.5; - - return file_time; -} - -static void -faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) -{ - GError *error = NULL; - float total_time = 0; - faacDecHandle decoder; - struct audio_format audio_format; - faacDecConfigurationPtr config; - bool ret; - uint16_t bit_rate = 0; - struct decoder_buffer *buffer; - enum decoder_command cmd; - - buffer = decoder_buffer_new(mpd_decoder, is, - FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - total_time = faad_song_duration(buffer, is); - - /* create the libfaad decoder */ - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; -#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX - config->downMatrix = 1; -#endif -#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR - config->dontUpSampleImplicitSBR = 0; -#endif - faacDecSetConfiguration(decoder, config); - - while (!decoder_buffer_is_full(buffer) && - !input_stream_eof(is) && - decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { - adts_find_frame(buffer); - decoder_buffer_fill(buffer); - } - - /* initialize it */ - - ret = faad_decoder_init(decoder, buffer, &audio_format, &error); - if (!ret) { - g_warning("%s", error->message); - g_error_free(error); - faacDecClose(decoder); - return; - } - - /* initialize the MPD core */ - - decoder_initialized(mpd_decoder, &audio_format, false, total_time); - - /* the decoder loop */ - - do { - size_t frame_size; - const void *decoded; - faacDecFrameInfo frame_info; - - /* find the next frame */ - - frame_size = adts_find_frame(buffer); - if (frame_size == 0) - /* end of file */ - break; - - /* decode it */ - - decoded = faad_decoder_decode(decoder, buffer, &frame_info); - - if (frame_info.error > 0) { - g_warning("error decoding AAC stream: %s\n", - faacDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - g_warning("channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - if (frame_info.samplerate != audio_format.sample_rate) { - g_warning("sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } -#endif - - decoder_buffer_consume(buffer, frame_info.bytesconsumed); - - /* update bit rate and position */ - - if (frame_info.samples > 0) { - bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * audio_format.sample_rate / - frame_info.samples / 1000 + 0.5; - } - - /* send PCM samples to MPD */ - - cmd = decoder_data(mpd_decoder, is, decoded, - (size_t)frame_info.samples * 2, - bit_rate); - } while (cmd != DECODE_COMMAND_STOP); - - /* cleanup */ - - faacDecClose(decoder); -} - -static struct tag * -faad_stream_tag(struct input_stream *is) -{ - int file_time = faad_get_file_time(is); - struct tag *tag; - - if (file_time < 0) - return NULL; - - tag = tag_new(); - tag->time = file_time; - return tag; -} - -static const char *const faad_suffixes[] = { "aac", NULL }; -static const char *const faad_mime_types[] = { - "audio/aac", "audio/aacp", NULL -}; - -const struct decoder_plugin faad_decoder_plugin = { - .name = "faad", - .stream_decode = faad_stream_decode, - .stream_tag = faad_stream_tag, - .suffixes = faad_suffixes, - .mime_types = faad_mime_types, -}; diff --git a/src/decoder/ffmpeg_decoder_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c new file mode 100644 index 000000000..99fbc2108 --- /dev/null +++ b/src/decoder/ffmpeg_decoder_plugin.c @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef OLD_FFMPEG_INCLUDES +#include +#include +#include +#else +#include +#include +#include +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "ffmpeg" + +struct ffmpeg_context { + int audio_stream; + AVFormatContext *format_context; + AVCodecContext *codec_context; + struct decoder *decoder; + struct input_stream *input; + struct tag *tag; +}; + +struct ffmpeg_stream { + /** hack - see url_to_struct() */ + char url[8]; + + struct decoder *decoder; + struct input_stream *input; +}; + +/** + * Convert a faked mpd:// URL to a ffmpeg_stream structure. This is a + * hack because ffmpeg does not provide a nice API for passing a + * user-defined pointer to mpdurl_open(). + */ +static struct ffmpeg_stream *url_to_struct(const char *url) +{ + union { + const char *in; + struct ffmpeg_stream *out; + } u = { .in = url }; + return u.out; +} + +static int mpd_ffmpeg_open(URLContext *h, const char *filename, + G_GNUC_UNUSED int flags) +{ + struct ffmpeg_stream *stream = url_to_struct(filename); + h->priv_data = stream; + h->is_streamed = stream->input->seekable ? 0 : 1; + return 0; +} + +static int mpd_ffmpeg_read(URLContext *h, unsigned char *buf, int size) +{ + struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data; + + return decoder_read(stream->decoder, stream->input, + (void *)buf, size); +} + +static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence) +{ + struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data; + bool ret; + + if (whence == AVSEEK_SIZE) + return stream->input->size; + + ret = input_stream_seek(stream->input, pos, whence, NULL); + if (!ret) + return -1; + + return stream->input->offset; +} + +static int mpd_ffmpeg_close(URLContext *h) +{ + h->priv_data = NULL; + return 0; +} + +static URLProtocol mpd_ffmpeg_fileops = { + .name = "mpd", + .url_open = mpd_ffmpeg_open, + .url_read = mpd_ffmpeg_read, + .url_seek = mpd_ffmpeg_seek, + .url_close = mpd_ffmpeg_close, +}; + +static bool +ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) +{ + av_register_all(); + register_protocol(&mpd_ffmpeg_fileops); + return true; +} + +static int +ffmpeg_find_audio_stream(const AVFormatContext *format_context) +{ + for (unsigned i = 0; i < format_context->nb_streams; ++i) + if (format_context->streams[i]->codec->codec_type == + CODEC_TYPE_AUDIO) + return i; + + return -1; +} + +static bool +ffmpeg_helper(struct input_stream *input, + bool (*callback)(struct ffmpeg_context *ctx), + struct ffmpeg_context *ctx) +{ + AVFormatContext *format_context; + AVCodecContext *codec_context; + AVCodec *codec; + int audio_stream; + struct ffmpeg_stream stream = { + .url = "mpd://X", /* only the mpd:// prefix matters */ + }; + bool ret; + + stream.input = input; + if (ctx && ctx->decoder) { + stream.decoder = ctx->decoder; //are we in decoding loop ? + } else { + stream.decoder = NULL; + } + + //ffmpeg works with ours "fileops" helper + if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) { + g_warning("Open failed\n"); + return false; + } + + if (av_find_stream_info(format_context)<0) { + g_warning("Couldn't find stream info\n"); + return false; + } + + audio_stream = ffmpeg_find_audio_stream(format_context); + if (audio_stream == -1) { + g_warning("No audio stream inside\n"); + return false; + } + + codec_context = format_context->streams[audio_stream]->codec; + if (codec_context->codec_name[0] != 0) + g_debug("codec '%s'", codec_context->codec_name); + + codec = avcodec_find_decoder(codec_context->codec_id); + + if (!codec) { + g_warning("Unsupported audio codec\n"); + return false; + } + + if (avcodec_open(codec_context, codec)<0) { + g_warning("Could not open codec\n"); + return false; + } + + if (callback) { + ctx->audio_stream = audio_stream; + ctx->format_context = format_context; + ctx->codec_context = codec_context; + + ret = callback(ctx); + } else + ret = true; + + avcodec_close(codec_context); + av_close_input_file(format_context); + + return ret; +} + +/** + * On some platforms, libavcodec wants the output buffer aligned to 16 + * bytes (because it uses SSE/Altivec internally). This function + * returns the aligned version of the specified buffer, and corrects + * the buffer size. + */ +static void * +align16(void *p, size_t *length_p) +{ + unsigned add = 16 - (size_t)p % 16; + + *length_p -= add; + return (char *)p + add; +} + +static enum decoder_command +ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVRational *time_base) +{ + enum decoder_command cmd = DECODE_COMMAND_NONE; + uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; + int16_t *aligned_buffer; + size_t buffer_size; + int len, audio_size; + uint8_t *packet_data; + int packet_size; + + packet_data = packet->data; + packet_size = packet->size; + + buffer_size = sizeof(audio_buf); + aligned_buffer = align16(audio_buf, &buffer_size); + + while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) { + audio_size = buffer_size; + len = avcodec_decode_audio2(codec_context, + aligned_buffer, &audio_size, + packet_data, packet_size); + + if (len < 0) { + /* if error, we skip the frame */ + g_message("decoding failed\n"); + break; + } + + packet_data += len; + packet_size -= len; + + if (audio_size <= 0) + continue; + + if (packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + av_rescale_q(packet->pts, *time_base, + (AVRational){1, 1})); + + cmd = decoder_data(decoder, is, + aligned_buffer, audio_size, + codec_context->bit_rate / 1000); + } + return cmd; +} + +static enum sample_format +ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) +{ +#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0) + int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt); + + /* XXX implement & test other sample formats */ + + switch (bits) { + case 16: + return SAMPLE_FORMAT_S16; + } + + return SAMPLE_FORMAT_UNDEFINED; +#else + /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ + return SAMPLE_FORMAT_S16; +#endif +} + +static bool +ffmpeg_decode_internal(struct ffmpeg_context *ctx) +{ + GError *error = NULL; + struct decoder *decoder = ctx->decoder; + AVCodecContext *codec_context = ctx->codec_context; + AVFormatContext *format_context = ctx->format_context; + AVPacket packet; + struct audio_format audio_format; + enum decoder_command cmd; + int total_time; + + total_time = 0; + + if (!audio_format_init_checked(&audio_format, + codec_context->sample_rate, + ffmpeg_sample_format(codec_context), + codec_context->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return false; + } + + //there is some problem with this on some demux (mp3 at least) + if (format_context->duration != (int64_t)AV_NOPTS_VALUE) { + total_time = format_context->duration / AV_TIME_BASE; + } + + decoder_initialized(decoder, &audio_format, + ctx->input->seekable, total_time); + + do { + if (av_read_frame(format_context, &packet) < 0) + /* end of file */ + break; + + if (packet.stream_index == ctx->audio_stream) + cmd = ffmpeg_send_packet(decoder, ctx->input, + &packet, codec_context, + &format_context->streams[ctx->audio_stream]->time_base); + else + cmd = decoder_get_command(decoder); + + av_free_packet(&packet); + + if (cmd == DECODE_COMMAND_SEEK) { + int64_t where = + decoder_seek_where(decoder) * AV_TIME_BASE; + + if (av_seek_frame(format_context, -1, where, 0) < 0) + decoder_seek_error(decoder); + else + decoder_command_finished(decoder); + } + } while (cmd != DECODE_COMMAND_STOP); + + return true; +} + +static void +ffmpeg_decode(struct decoder *decoder, struct input_stream *input) +{ + struct ffmpeg_context ctx; + + ctx.input = input; + ctx.decoder = decoder; + + ffmpeg_helper(input, ffmpeg_decode_internal, &ctx); +} + +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) +static bool +ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m, + enum tag_type type, const char *name) +{ + AVMetadataTag *mt = NULL; + + while ((mt = av_metadata_get(m, name, mt, 0)) != NULL) + tag_add_item(tag, type, mt->value); + return mt != NULL; +} +#endif + +static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx) +{ + struct tag *tag = (struct tag *) ctx->tag; + AVFormatContext *f = ctx->format_context; + + tag->time = 0; + if (f->duration != (int64_t)AV_NOPTS_VALUE) + tag->time = f->duration / AV_TIME_BASE; + +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) + av_metadata_conv(f, NULL, f->iformat->metadata_conv); + + ffmpeg_copy_metadata(tag, f->metadata, TAG_TITLE, "title"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "author"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM, "album"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_COMMENT, "comment"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_GENRE, "genre"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_TRACK, "track"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "year"); +#else + if (f->author[0]) + tag_add_item(tag, TAG_ARTIST, f->author); + if (f->title[0]) + tag_add_item(tag, TAG_TITLE, f->title); + if (f->album[0]) + tag_add_item(tag, TAG_ALBUM, f->album); + + if (f->track > 0) { + char buffer[16]; + snprintf(buffer, sizeof(buffer), "%d", f->track); + tag_add_item(tag, TAG_TRACK, buffer); + } + + if (f->comment[0]) + tag_add_item(tag, TAG_COMMENT, f->comment); + if (f->genre[0]) + tag_add_item(tag, TAG_GENRE, f->genre); + if (f->year > 0) { + char buffer[16]; + snprintf(buffer, sizeof(buffer), "%d", f->year); + tag_add_item(tag, TAG_DATE, buffer); + } + +#endif + return true; +} + +//no tag reading in ffmpeg, check if playable +static struct tag * +ffmpeg_stream_tag(struct input_stream *is) +{ + struct ffmpeg_context ctx; + bool ret; + + ctx.decoder = NULL; + ctx.tag = tag_new(); + + ret = ffmpeg_helper(is, ffmpeg_tag_internal, &ctx); + if (!ret) { + tag_free(ctx.tag); + ctx.tag = NULL; + } + + return ctx.tag; +} + +/** + * A list of extensions found for the formats supported by ffmpeg. + * This list is current as of 02-23-09; To find out if there are more + * supported formats, check the ffmpeg changelog since this date for + * more formats. + */ +static const char *const ffmpeg_suffixes[] = { + "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", + "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", + "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", + "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", + "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", + "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4v", "mad", + "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", + "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", + "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", + "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", + "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", + "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", + "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", + "vp6", "vmd", "wav", "wma", "wmv", "wsaud", "wsvga", "wv", "wve", + NULL +}; + +static const char *const ffmpeg_mime_types[] = { + "application/mp4", + "application/octet-stream", + "application/ogg", + "application/x-ms-wmz", + "application/x-ms-wmd", + "application/x-ogg", + "application/x-shockwave-flash", + "application/x-shorten", + "audio/8svx", + "audio/16sv", + "audio/aac", + "audio/ac3", + "audio/amr", + "audio/basic", + "audio/flac", + "audio/mpeg", + "audio/musepack", + "audio/ogg", + "audio/qcelp", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-8svx", + "audio/x-16sv", + "audio/x-aac", + "audio/x-ac3", + "audio/x-aiff" + "audio/x-alaw", + "audio/x-au", + "audio/x-dca", + "audio/x-eac3", + "audio/x-flac", + "audio/x-gsm", + "audio/x-mace", + "audio/x-monkeys-audio", + "audio/x-mpeg", + "audio/x-ms-wma", + "audio/x-ms-wax", + "audio/x-musepack", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + "audio/x-pn-realaudio", + "audio/x-pn-multirate-realaudio", + "audio/x-speex", + "audio/x-tta" + "audio/x-wav", + "audio/x-wma", + "audio/x-wv", + "video/anim", + "video/quicktime", + "video/msvideo", + "video/ogg", + "video/theora", + "video/x-dv", + "video/x-flv", + "video/x-matroska", + "video/x-mjpeg", + "video/x-mpeg", + "video/x-ms-asf", + "video/x-msvideo", + "video/x-ms-wmv", + "video/x-ms-wvx", + "video/x-ms-wm", + "video/x-ms-wmx", + "video/x-nut", + "video/x-pva", + "video/x-theora", + "video/x-vid", + "video/x-wmv", + "video/x-xvid", + NULL +}; + +const struct decoder_plugin ffmpeg_decoder_plugin = { + .name = "ffmpeg", + .init = ffmpeg_init, + .stream_decode = ffmpeg_decode, + .stream_tag = ffmpeg_stream_tag, + .suffixes = ffmpeg_suffixes, + .mime_types = ffmpeg_mime_types +}; diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c deleted file mode 100644 index 99fbc2108..000000000 --- a/src/decoder/ffmpeg_plugin.c +++ /dev/null @@ -1,552 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef OLD_FFMPEG_INCLUDES -#include -#include -#include -#else -#include -#include -#include -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ffmpeg" - -struct ffmpeg_context { - int audio_stream; - AVFormatContext *format_context; - AVCodecContext *codec_context; - struct decoder *decoder; - struct input_stream *input; - struct tag *tag; -}; - -struct ffmpeg_stream { - /** hack - see url_to_struct() */ - char url[8]; - - struct decoder *decoder; - struct input_stream *input; -}; - -/** - * Convert a faked mpd:// URL to a ffmpeg_stream structure. This is a - * hack because ffmpeg does not provide a nice API for passing a - * user-defined pointer to mpdurl_open(). - */ -static struct ffmpeg_stream *url_to_struct(const char *url) -{ - union { - const char *in; - struct ffmpeg_stream *out; - } u = { .in = url }; - return u.out; -} - -static int mpd_ffmpeg_open(URLContext *h, const char *filename, - G_GNUC_UNUSED int flags) -{ - struct ffmpeg_stream *stream = url_to_struct(filename); - h->priv_data = stream; - h->is_streamed = stream->input->seekable ? 0 : 1; - return 0; -} - -static int mpd_ffmpeg_read(URLContext *h, unsigned char *buf, int size) -{ - struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data; - - return decoder_read(stream->decoder, stream->input, - (void *)buf, size); -} - -static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence) -{ - struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data; - bool ret; - - if (whence == AVSEEK_SIZE) - return stream->input->size; - - ret = input_stream_seek(stream->input, pos, whence, NULL); - if (!ret) - return -1; - - return stream->input->offset; -} - -static int mpd_ffmpeg_close(URLContext *h) -{ - h->priv_data = NULL; - return 0; -} - -static URLProtocol mpd_ffmpeg_fileops = { - .name = "mpd", - .url_open = mpd_ffmpeg_open, - .url_read = mpd_ffmpeg_read, - .url_seek = mpd_ffmpeg_seek, - .url_close = mpd_ffmpeg_close, -}; - -static bool -ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) -{ - av_register_all(); - register_protocol(&mpd_ffmpeg_fileops); - return true; -} - -static int -ffmpeg_find_audio_stream(const AVFormatContext *format_context) -{ - for (unsigned i = 0; i < format_context->nb_streams; ++i) - if (format_context->streams[i]->codec->codec_type == - CODEC_TYPE_AUDIO) - return i; - - return -1; -} - -static bool -ffmpeg_helper(struct input_stream *input, - bool (*callback)(struct ffmpeg_context *ctx), - struct ffmpeg_context *ctx) -{ - AVFormatContext *format_context; - AVCodecContext *codec_context; - AVCodec *codec; - int audio_stream; - struct ffmpeg_stream stream = { - .url = "mpd://X", /* only the mpd:// prefix matters */ - }; - bool ret; - - stream.input = input; - if (ctx && ctx->decoder) { - stream.decoder = ctx->decoder; //are we in decoding loop ? - } else { - stream.decoder = NULL; - } - - //ffmpeg works with ours "fileops" helper - if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) { - g_warning("Open failed\n"); - return false; - } - - if (av_find_stream_info(format_context)<0) { - g_warning("Couldn't find stream info\n"); - return false; - } - - audio_stream = ffmpeg_find_audio_stream(format_context); - if (audio_stream == -1) { - g_warning("No audio stream inside\n"); - return false; - } - - codec_context = format_context->streams[audio_stream]->codec; - if (codec_context->codec_name[0] != 0) - g_debug("codec '%s'", codec_context->codec_name); - - codec = avcodec_find_decoder(codec_context->codec_id); - - if (!codec) { - g_warning("Unsupported audio codec\n"); - return false; - } - - if (avcodec_open(codec_context, codec)<0) { - g_warning("Could not open codec\n"); - return false; - } - - if (callback) { - ctx->audio_stream = audio_stream; - ctx->format_context = format_context; - ctx->codec_context = codec_context; - - ret = callback(ctx); - } else - ret = true; - - avcodec_close(codec_context); - av_close_input_file(format_context); - - return ret; -} - -/** - * On some platforms, libavcodec wants the output buffer aligned to 16 - * bytes (because it uses SSE/Altivec internally). This function - * returns the aligned version of the specified buffer, and corrects - * the buffer size. - */ -static void * -align16(void *p, size_t *length_p) -{ - unsigned add = 16 - (size_t)p % 16; - - *length_p -= add; - return (char *)p + add; -} - -static enum decoder_command -ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVRational *time_base) -{ - enum decoder_command cmd = DECODE_COMMAND_NONE; - uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; - int16_t *aligned_buffer; - size_t buffer_size; - int len, audio_size; - uint8_t *packet_data; - int packet_size; - - packet_data = packet->data; - packet_size = packet->size; - - buffer_size = sizeof(audio_buf); - aligned_buffer = align16(audio_buf, &buffer_size); - - while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) { - audio_size = buffer_size; - len = avcodec_decode_audio2(codec_context, - aligned_buffer, &audio_size, - packet_data, packet_size); - - if (len < 0) { - /* if error, we skip the frame */ - g_message("decoding failed\n"); - break; - } - - packet_data += len; - packet_size -= len; - - if (audio_size <= 0) - continue; - - if (packet->pts != (int64_t)AV_NOPTS_VALUE) - decoder_timestamp(decoder, - av_rescale_q(packet->pts, *time_base, - (AVRational){1, 1})); - - cmd = decoder_data(decoder, is, - aligned_buffer, audio_size, - codec_context->bit_rate / 1000); - } - return cmd; -} - -static enum sample_format -ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) -{ -#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0) - int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt); - - /* XXX implement & test other sample formats */ - - switch (bits) { - case 16: - return SAMPLE_FORMAT_S16; - } - - return SAMPLE_FORMAT_UNDEFINED; -#else - /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ - return SAMPLE_FORMAT_S16; -#endif -} - -static bool -ffmpeg_decode_internal(struct ffmpeg_context *ctx) -{ - GError *error = NULL; - struct decoder *decoder = ctx->decoder; - AVCodecContext *codec_context = ctx->codec_context; - AVFormatContext *format_context = ctx->format_context; - AVPacket packet; - struct audio_format audio_format; - enum decoder_command cmd; - int total_time; - - total_time = 0; - - if (!audio_format_init_checked(&audio_format, - codec_context->sample_rate, - ffmpeg_sample_format(codec_context), - codec_context->channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return false; - } - - //there is some problem with this on some demux (mp3 at least) - if (format_context->duration != (int64_t)AV_NOPTS_VALUE) { - total_time = format_context->duration / AV_TIME_BASE; - } - - decoder_initialized(decoder, &audio_format, - ctx->input->seekable, total_time); - - do { - if (av_read_frame(format_context, &packet) < 0) - /* end of file */ - break; - - if (packet.stream_index == ctx->audio_stream) - cmd = ffmpeg_send_packet(decoder, ctx->input, - &packet, codec_context, - &format_context->streams[ctx->audio_stream]->time_base); - else - cmd = decoder_get_command(decoder); - - av_free_packet(&packet); - - if (cmd == DECODE_COMMAND_SEEK) { - int64_t where = - decoder_seek_where(decoder) * AV_TIME_BASE; - - if (av_seek_frame(format_context, -1, where, 0) < 0) - decoder_seek_error(decoder); - else - decoder_command_finished(decoder); - } - } while (cmd != DECODE_COMMAND_STOP); - - return true; -} - -static void -ffmpeg_decode(struct decoder *decoder, struct input_stream *input) -{ - struct ffmpeg_context ctx; - - ctx.input = input; - ctx.decoder = decoder; - - ffmpeg_helper(input, ffmpeg_decode_internal, &ctx); -} - -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) -static bool -ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m, - enum tag_type type, const char *name) -{ - AVMetadataTag *mt = NULL; - - while ((mt = av_metadata_get(m, name, mt, 0)) != NULL) - tag_add_item(tag, type, mt->value); - return mt != NULL; -} -#endif - -static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx) -{ - struct tag *tag = (struct tag *) ctx->tag; - AVFormatContext *f = ctx->format_context; - - tag->time = 0; - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag->time = f->duration / AV_TIME_BASE; - -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) - av_metadata_conv(f, NULL, f->iformat->metadata_conv); - - ffmpeg_copy_metadata(tag, f->metadata, TAG_TITLE, "title"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "author"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM, "album"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_COMMENT, "comment"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_GENRE, "genre"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_TRACK, "track"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "year"); -#else - if (f->author[0]) - tag_add_item(tag, TAG_ARTIST, f->author); - if (f->title[0]) - tag_add_item(tag, TAG_TITLE, f->title); - if (f->album[0]) - tag_add_item(tag, TAG_ALBUM, f->album); - - if (f->track > 0) { - char buffer[16]; - snprintf(buffer, sizeof(buffer), "%d", f->track); - tag_add_item(tag, TAG_TRACK, buffer); - } - - if (f->comment[0]) - tag_add_item(tag, TAG_COMMENT, f->comment); - if (f->genre[0]) - tag_add_item(tag, TAG_GENRE, f->genre); - if (f->year > 0) { - char buffer[16]; - snprintf(buffer, sizeof(buffer), "%d", f->year); - tag_add_item(tag, TAG_DATE, buffer); - } - -#endif - return true; -} - -//no tag reading in ffmpeg, check if playable -static struct tag * -ffmpeg_stream_tag(struct input_stream *is) -{ - struct ffmpeg_context ctx; - bool ret; - - ctx.decoder = NULL; - ctx.tag = tag_new(); - - ret = ffmpeg_helper(is, ffmpeg_tag_internal, &ctx); - if (!ret) { - tag_free(ctx.tag); - ctx.tag = NULL; - } - - return ctx.tag; -} - -/** - * A list of extensions found for the formats supported by ffmpeg. - * This list is current as of 02-23-09; To find out if there are more - * supported formats, check the ffmpeg changelog since this date for - * more formats. - */ -static const char *const ffmpeg_suffixes[] = { - "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", - "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", - "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", - "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", - "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", - "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4v", "mad", - "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", - "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", - "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", - "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", - "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", - "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", - "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", - "vp6", "vmd", "wav", "wma", "wmv", "wsaud", "wsvga", "wv", "wve", - NULL -}; - -static const char *const ffmpeg_mime_types[] = { - "application/mp4", - "application/octet-stream", - "application/ogg", - "application/x-ms-wmz", - "application/x-ms-wmd", - "application/x-ogg", - "application/x-shockwave-flash", - "application/x-shorten", - "audio/8svx", - "audio/16sv", - "audio/aac", - "audio/ac3", - "audio/amr", - "audio/basic", - "audio/flac", - "audio/mpeg", - "audio/musepack", - "audio/ogg", - "audio/qcelp", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-8svx", - "audio/x-16sv", - "audio/x-aac", - "audio/x-ac3", - "audio/x-aiff" - "audio/x-alaw", - "audio/x-au", - "audio/x-dca", - "audio/x-eac3", - "audio/x-flac", - "audio/x-gsm", - "audio/x-mace", - "audio/x-monkeys-audio", - "audio/x-mpeg", - "audio/x-ms-wma", - "audio/x-ms-wax", - "audio/x-musepack", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - "audio/x-pn-realaudio", - "audio/x-pn-multirate-realaudio", - "audio/x-speex", - "audio/x-tta" - "audio/x-wav", - "audio/x-wma", - "audio/x-wv", - "video/anim", - "video/quicktime", - "video/msvideo", - "video/ogg", - "video/theora", - "video/x-dv", - "video/x-flv", - "video/x-matroska", - "video/x-mjpeg", - "video/x-mpeg", - "video/x-ms-asf", - "video/x-msvideo", - "video/x-ms-wmv", - "video/x-ms-wvx", - "video/x-ms-wm", - "video/x-ms-wmx", - "video/x-nut", - "video/x-pva", - "video/x-theora", - "video/x-vid", - "video/x-wmv", - "video/x-xvid", - NULL -}; - -const struct decoder_plugin ffmpeg_decoder_plugin = { - .name = "ffmpeg", - .init = ffmpeg_init, - .stream_decode = ffmpeg_decode, - .stream_tag = ffmpeg_stream_tag, - .suffixes = ffmpeg_suffixes, - .mime_types = ffmpeg_mime_types -}; diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c new file mode 100644 index 000000000..df5778e03 --- /dev/null +++ b/src/decoder/flac_decoder_plugin.c @@ -0,0 +1,838 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "_flac_common.h" +#include "flac_compat.h" +#include "flac_metadata.h" + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 +#include "_ogg_common.h" +#endif + +#include + +#include +#include + +#include +#include + +#ifdef HAVE_CUE /* libcue */ +#include "../cue/cue_tag.h" +#endif + +/* this code was based on flac123, from flac-tools */ + +static FLAC__StreamDecoderReadStatus +flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__byte buf[], flac_read_status_size_t *bytes, + void *fdata) +{ + struct flac_data *data = fdata; + size_t r; + + r = decoder_read(data->decoder, data->input_stream, + (void *)buf, *bytes); + *bytes = r; + + if (r == 0) { + if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || + input_stream_eof(data->input_stream)) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderSeekStatus +flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__uint64 offset, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (!data->input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +static FLAC__StreamDecoderTellStatus +flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__uint64 * offset, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (!data->input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + *offset = (long)(data->input_stream->offset); + + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +static FLAC__StreamDecoderLengthStatus +flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__uint64 * length, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (data->input_stream->size < 0) + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + + *length = (size_t) (data->input_stream->size); + + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool +flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && + decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || + input_stream_eof(data->input_stream); +} + +static void +flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("flac", status, (struct flac_data *) 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"; + } + + g_warning("%s\n", str); +} +#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"; + } + + g_warning("%s\n", str); +} +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, + const FLAC__StreamMetadata * block, void *vdata) +{ + flac_metadata_common_cb(block, (struct flac_data *) vdata); +} + +static FLAC__StreamDecoderWriteStatus +flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, + const FLAC__int32 *const buf[], void *vdata) +{ + struct flac_data *data = (struct flac_data *) vdata; + FLAC__uint64 nbytes = 0; + + if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { + if (data->position > 0 && nbytes > data->position) { + nbytes -= data->position; + data->position += nbytes; + } else { + data->position = nbytes; + nbytes = 0; + } + } else + nbytes = 0; + + return flac_common_write(data, frame, buf, nbytes); +} + +static struct tag * +flac_tag_load(const char *file, const char *char_tnum) +{ + struct tag *tag; + FLAC__Metadata_SimpleIterator *it; + FLAC__StreamMetadata *block = NULL; + + 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]; + } + g_debug("Reading '%s' metadata gave the following error: %s\n", + file, err); + FLAC__metadata_simple_iterator_delete(it); + return NULL; + } + + tag = tag_new(); + do { + block = FLAC__metadata_simple_iterator_get_block(it); + if (!block) + break; + + flac_tag_apply_metadata(tag, char_tnum, block); + FLAC__metadata_object_delete(block); + } while (FLAC__metadata_simple_iterator_next(it)); + + FLAC__metadata_simple_iterator_delete(it); + + if (!tag_is_defined(tag)) { + tag_free(tag); + tag = NULL; + } + + return tag; +} + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +static struct tag * +flac_cue_tag_load(const char *file) +{ + struct tag* tag = NULL; + char* char_tnum = NULL; + char* ptr = NULL; + unsigned int tnum = 0; + unsigned int sample_rate = 0; + FLAC__uint64 track_time = 0; +#ifdef HAVE_CUE /* libcue */ + FLAC__StreamMetadata* vc; + char* cs_filename; + FILE* cs_file; +#endif /* libcue */ + FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO); + FLAC__StreamMetadata* cs; + + tnum = flac_vtrack_tnum(file); + char_tnum = g_strdup_printf("%u", tnum); + + ptr = strrchr(file, '/'); + *ptr = '\0'; + +#ifdef HAVE_CUE /* libcue */ + if (FLAC__metadata_get_tags(file, &vc)) + { + for (unsigned i = 0; i < vc->data.vorbis_comment.num_comments; + i++) + { + if ((ptr = (char*)vc->data.vorbis_comment.comments[i].entry) != NULL) + { + if (g_ascii_strncasecmp(ptr, "cuesheet", 8) == 0) + { + while (*(++ptr) != '='); + tag = cue_tag_string( ++ptr, + tnum); + } + } + } + + FLAC__metadata_object_delete(vc); + } + + if (tag == NULL) { + cs_filename = g_strconcat(file, ".cue", NULL); + + cs_file = fopen(cs_filename, "rt"); + g_free(cs_filename); + + if (cs_file != NULL) { + tag = cue_tag_file(cs_file, tnum); + fclose(cs_file); + } + } +#endif /* libcue */ + + if (tag == NULL) + tag = flac_tag_load(file, char_tnum); + + if (char_tnum != NULL) { + tag_add_item(tag, TAG_TRACK, char_tnum); + g_free(char_tnum); + } + + if (FLAC__metadata_get_streaminfo(file, si)) + { + sample_rate = si->data.stream_info.sample_rate; + FLAC__metadata_object_delete(si); + } + + if (FLAC__metadata_get_cuesheet(file, &cs)) + { + if (cs->data.cue_sheet.tracks != NULL + && (tnum <= cs->data.cue_sheet.num_tracks - 1)) + { + track_time = cs->data.cue_sheet.tracks[tnum].offset + - cs->data.cue_sheet.tracks[tnum - 1].offset; + } + FLAC__metadata_object_delete(cs); + } + + if (sample_rate != 0) + { + tag->time = (unsigned int)(track_time/sample_rate); + } + + return tag; +} + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static struct tag * +flac_tag_dup(const char *file) +{ +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + struct stat st; + + if (stat(file, &st) < 0) + return flac_cue_tag_load(file); + else +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + return flac_tag_load(file, NULL); +} + +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) +{ + FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); + if (sd == NULL) { + g_warning("FLAC__stream_decoder_new() failed"); + return NULL; + } + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); +#endif + + return sd; +} + +static bool +flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, + bool seekable, FLAC__uint64 duration) +{ + struct audio_format audio_format; + + if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { + g_warning("problem reading metadata"); + return false; + } + + if (!flac_data_get_audio_format(data, &audio_format)) + return false; + + if (duration == 0) + duration = data->stream_info.total_samples; + + decoder_initialized(data->decoder, &audio_format, + seekable, + (float)duration / + (float)data->stream_info.sample_rate); + return true; +} + +static void +flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, + FLAC__uint64 t_start, FLAC__uint64 t_end) +{ + struct decoder *decoder = data->decoder; + enum decoder_command cmd; + + data->first_frame = t_start; + + while (true) { + if (data->tag != NULL && !tag_is_empty(data->tag)) { + cmd = decoder_tag(data->decoder, data->input_stream, + data->tag); + tag_free(data->tag); + data->tag = tag_new(); + } else + cmd = decoder_get_command(decoder); + + if (cmd == DECODE_COMMAND_SEEK) { + FLAC__uint64 seek_sample = t_start + + decoder_seek_where(decoder) * + data->stream_info.sample_rate; + if (seek_sample >= t_start && + (t_end == 0 || seek_sample <= t_end) && + FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { + data->next_frame = seek_sample; + data->position = 0; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else if (cmd == DECODE_COMMAND_STOP || + FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) + break; + + if (t_end != 0 && data->next_frame >= t_end) + /* end of this sub track */ + break; + + if (!FLAC__stream_decoder_process_single(flac_dec)) { + cmd = decoder_get_command(decoder); + if (cmd != DECODE_COMMAND_SEEK) + break; + } + } + + if (cmd != DECODE_COMMAND_STOP) { + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + FLAC__stream_decoder_finish(flac_dec); + } +} + +static void +flac_decode_internal(struct decoder * decoder, + struct input_stream *input_stream, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + struct flac_data data; + const char *err = NULL; + + flac_dec = flac_decoder_new(); + if (flac_dec == NULL) + return; + + flac_data_init(&data, decoder, input_stream); + data.tag = tag_new(); + + if (is_ogg) { +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + FLAC__StreamDecoderInitStatus status = + FLAC__stream_decoder_init_ogg_stream(flac_dec, + flac_read_cb, + flac_seek_cb, + flac_tell_cb, + flac_length_cb, + flac_eof_cb, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void *)&data); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing Ogg init()"; + goto fail; + } +#else + goto fail; +#endif + } else { + FLAC__StreamDecoderInitStatus status = + FLAC__stream_decoder_init_stream(flac_dec, + flac_read_cb, + flac_seek_cb, + flac_tell_cb, + flac_length_cb, + flac_eof_cb, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void *)&data); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing init()"; + goto fail; + } + } + + if (!flac_decoder_initialize(&data, flac_dec, + input_stream->seekable, 0)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + flac_decoder_loop(&data, flac_dec, 0, 0); + +fail: + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + + if (err) + g_warning("%s\n", err); +} + +static void +flac_decode(struct decoder * decoder, struct input_stream *input_stream) +{ + flac_decode_internal(decoder, input_stream, false); +} + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +/** + * @brief Decode a flac file with embedded cue sheets + * @param const char* fname filename on fs + */ +static void +flac_container_decode(struct decoder* decoder, + const char* fname, + bool is_ogg) +{ + unsigned int tnum = 0; + FLAC__uint64 t_start = 0; + FLAC__uint64 t_end = 0; + FLAC__uint64 track_time = 0; + FLAC__StreamMetadata* cs = NULL; + + FLAC__StreamDecoder *flac_dec; + FLAC__StreamDecoderInitStatus init_status; + struct flac_data data; + const char *err = NULL; + + char* pathname = g_strdup(fname); + char* slash = strrchr(pathname, '/'); + *slash = '\0'; + + tnum = flac_vtrack_tnum(fname); + + cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); + + FLAC__metadata_get_cuesheet(pathname, &cs); + + if (cs != NULL) + { + if (cs->data.cue_sheet.tracks != NULL + && (tnum <= cs->data.cue_sheet.num_tracks - 1)) + { + t_start = cs->data.cue_sheet.tracks[tnum - 1].offset; + t_end = cs->data.cue_sheet.tracks[tnum].offset; + track_time = cs->data.cue_sheet.tracks[tnum].offset + - cs->data.cue_sheet.tracks[tnum - 1].offset; + } + + FLAC__metadata_object_delete(cs); + } + else + { + g_free(pathname); + return; + } + + flac_dec = flac_decoder_new(); + if (flac_dec == NULL) + return; + + flac_data_init(&data, decoder, NULL); + + init_status = is_ogg + ? FLAC__stream_decoder_init_ogg_file(flac_dec, pathname, + flac_write_cb, + flacMetadata, + flac_error_cb, + &data) + : FLAC__stream_decoder_init_file(flac_dec, + pathname, flac_write_cb, + flacMetadata, flac_error_cb, + &data); + g_free(pathname); + if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing init()"; + goto fail; + } + + if (!flac_decoder_initialize(&data, flac_dec, true, track_time)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + // seek to song start (order is important: after decoder init) + FLAC__stream_decoder_seek_absolute(flac_dec, (FLAC__uint64)t_start); + data.next_frame = t_start; + + flac_decoder_loop(&data, flac_dec, t_start, t_end); + +fail: + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + + if (err) + g_warning("%s\n", err); +} + +/** + * @brief Open a flac file for decoding + * @param const char* fname filename on fs + */ +static void +flac_filedecode_internal(struct decoder* decoder, + const char* fname, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + struct flac_data data; + const char *err = NULL; + unsigned int flac_err_state = 0; + + flac_dec = flac_decoder_new(); + if (flac_dec == NULL) + return; + + flac_data_init(&data, decoder, NULL); + + if (is_ogg) + { + if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec, + fname, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void*) &data )) + == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) + { + flac_container_decode(decoder, fname, is_ogg); + } + else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + err = "doing Ogg init()"; + goto fail; + } + } + else + { + if ( (flac_err_state = FLAC__stream_decoder_init_file( flac_dec, + fname, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void*) &data )) + == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) + { + flac_container_decode(decoder, fname, is_ogg); + } + else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + err = "doing init()"; + goto fail; + } + } + + if (!flac_decoder_initialize(&data, flac_dec, true, 0)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + flac_decoder_loop(&data, flac_dec, 0, 0); + +fail: + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + + if (err) + g_warning("%s\n", err); +} + +/** + * @brief wrapper function for + * flac_filedecode_internal method + * for decoding without ogg + */ +static void +flac_filedecode(struct decoder *decoder, const char *fname) +{ + struct stat st; + + if (stat(fname, &st) < 0) { + flac_container_decode(decoder, fname, false); + } else + flac_filedecode_internal(decoder, fname, false); +} + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +#ifndef HAVE_OGGFLAC + +static bool +oggflac_init(G_GNUC_UNUSED const struct config_param *param) +{ +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + return !!FLAC_API_SUPPORTS_OGG_FLAC; +#else + /* disable oggflac when libflac is too old */ + return false; +#endif +} + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +static struct tag * +oggflac_tag_dup(const 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); + + ret = tag_new(); + do { + if (!(block = FLAC__metadata_iterator_get_block(it))) + break; + + flac_tag_apply_metadata(ret, NULL, block); + } while (FLAC__metadata_iterator_next(it)); + FLAC__metadata_iterator_delete(it); + + if (!tag_is_defined(ret)) { + tag_free(ret); + ret = NULL; + } + +out: + FLAC__metadata_chain_delete(chain); + return ret; +} + +static void +oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) +{ + if (ogg_stream_type_detect(input_stream) != FLAC) + return; + + /* rewind the stream, because ogg_stream_type_detect() has + moved it */ + input_stream_seek(input_stream, 0, SEEK_SET, NULL); + + flac_decode_internal(decoder, input_stream, true); +} + +static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/x-flac+ogg", + "audio/x-ogg", + NULL +}; + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +const struct decoder_plugin oggflac_decoder_plugin = { + .name = "oggflac", + .init = oggflac_init, +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + .stream_decode = oggflac_decode, + .tag_dup = oggflac_tag_dup, + .suffixes = oggflac_suffixes, + .mime_types = oggflac_mime_types +#endif +}; + +#endif /* HAVE_OGGFLAC */ + +static const char *const flac_suffixes[] = { "flac", NULL }; +static const char *const flac_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + NULL +}; + +const struct decoder_plugin flac_decoder_plugin = { + .name = "flac", + .stream_decode = flac_decode, +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + .file_decode = flac_filedecode, +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + .tag_dup = flac_tag_dup, + .suffixes = flac_suffixes, + .mime_types = flac_mime_types, +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + .container_scan = flac_cue_track, +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ +}; diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c deleted file mode 100644 index df5778e03..000000000 --- a/src/decoder/flac_plugin.c +++ /dev/null @@ -1,838 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "flac_compat.h" -#include "flac_metadata.h" - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -#include "_ogg_common.h" -#endif - -#include - -#include -#include - -#include -#include - -#ifdef HAVE_CUE /* libcue */ -#include "../cue/cue_tag.h" -#endif - -/* this code was based on flac123, from flac-tools */ - -static FLAC__StreamDecoderReadStatus -flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, - void *fdata) -{ - struct flac_data *data = fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_eof(data->input_stream)) - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -} - -static FLAC__StreamDecoderSeekStatus -flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) - return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; - - return FLAC__STREAM_DECODER_SEEK_STATUS_OK; -} - -static FLAC__StreamDecoderTellStatus -flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!data->input_stream->seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - *offset = (long)(data->input_stream->offset); - - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -} - -static FLAC__StreamDecoderLengthStatus -flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__uint64 * length, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - - *length = (size_t) (data->input_stream->size); - - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -} - -static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb("flac", status, (struct flac_data *) 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"; - } - - g_warning("%s\n", str); -} -#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"; - } - - g_warning("%s\n", str); -} -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, - const FLAC__StreamMetadata * block, void *vdata) -{ - flac_metadata_common_cb(block, (struct flac_data *) vdata); -} - -static FLAC__StreamDecoderWriteStatus -flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, - const FLAC__int32 *const buf[], void *vdata) -{ - struct flac_data *data = (struct flac_data *) vdata; - FLAC__uint64 nbytes = 0; - - if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { - if (data->position > 0 && nbytes > data->position) { - nbytes -= data->position; - data->position += nbytes; - } else { - data->position = nbytes; - nbytes = 0; - } - } else - nbytes = 0; - - return flac_common_write(data, frame, buf, nbytes); -} - -static struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag; - FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; - - 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]; - } - g_debug("Reading '%s' metadata gave the following error: %s\n", - file, err); - FLAC__metadata_simple_iterator_delete(it); - return NULL; - } - - tag = tag_new(); - do { - block = FLAC__metadata_simple_iterator_get_block(it); - if (!block) - break; - - flac_tag_apply_metadata(tag, char_tnum, block); - FLAC__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - if (!tag_is_defined(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -static struct tag * -flac_cue_tag_load(const char *file) -{ - struct tag* tag = NULL; - char* char_tnum = NULL; - char* ptr = NULL; - unsigned int tnum = 0; - unsigned int sample_rate = 0; - FLAC__uint64 track_time = 0; -#ifdef HAVE_CUE /* libcue */ - FLAC__StreamMetadata* vc; - char* cs_filename; - FILE* cs_file; -#endif /* libcue */ - FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO); - FLAC__StreamMetadata* cs; - - tnum = flac_vtrack_tnum(file); - char_tnum = g_strdup_printf("%u", tnum); - - ptr = strrchr(file, '/'); - *ptr = '\0'; - -#ifdef HAVE_CUE /* libcue */ - if (FLAC__metadata_get_tags(file, &vc)) - { - for (unsigned i = 0; i < vc->data.vorbis_comment.num_comments; - i++) - { - if ((ptr = (char*)vc->data.vorbis_comment.comments[i].entry) != NULL) - { - if (g_ascii_strncasecmp(ptr, "cuesheet", 8) == 0) - { - while (*(++ptr) != '='); - tag = cue_tag_string( ++ptr, - tnum); - } - } - } - - FLAC__metadata_object_delete(vc); - } - - if (tag == NULL) { - cs_filename = g_strconcat(file, ".cue", NULL); - - cs_file = fopen(cs_filename, "rt"); - g_free(cs_filename); - - if (cs_file != NULL) { - tag = cue_tag_file(cs_file, tnum); - fclose(cs_file); - } - } -#endif /* libcue */ - - if (tag == NULL) - tag = flac_tag_load(file, char_tnum); - - if (char_tnum != NULL) { - tag_add_item(tag, TAG_TRACK, char_tnum); - g_free(char_tnum); - } - - if (FLAC__metadata_get_streaminfo(file, si)) - { - sample_rate = si->data.stream_info.sample_rate; - FLAC__metadata_object_delete(si); - } - - if (FLAC__metadata_get_cuesheet(file, &cs)) - { - if (cs->data.cue_sheet.tracks != NULL - && (tnum <= cs->data.cue_sheet.num_tracks - 1)) - { - track_time = cs->data.cue_sheet.tracks[tnum].offset - - cs->data.cue_sheet.tracks[tnum - 1].offset; - } - FLAC__metadata_object_delete(cs); - } - - if (sample_rate != 0) - { - tag->time = (unsigned int)(track_time/sample_rate); - } - - return tag; -} - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static struct tag * -flac_tag_dup(const char *file) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - struct stat st; - - if (stat(file, &st) < 0) - return flac_cue_tag_load(file); - else -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - return flac_tag_load(file, NULL); -} - -/** - * Some glue code around FLAC__stream_decoder_new(). - */ -static FLAC__StreamDecoder * -flac_decoder_new(void) -{ - FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == NULL) { - g_warning("FLAC__stream_decoder_new() failed"); - return NULL; - } - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); -#endif - - return sd; -} - -static bool -flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, - bool seekable, FLAC__uint64 duration) -{ - struct audio_format audio_format; - - if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { - g_warning("problem reading metadata"); - return false; - } - - if (!flac_data_get_audio_format(data, &audio_format)) - return false; - - if (duration == 0) - duration = data->stream_info.total_samples; - - decoder_initialized(data->decoder, &audio_format, - seekable, - (float)duration / - (float)data->stream_info.sample_rate); - return true; -} - -static void -flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, - FLAC__uint64 t_start, FLAC__uint64 t_end) -{ - struct decoder *decoder = data->decoder; - enum decoder_command cmd; - - data->first_frame = t_start; - - while (true) { - if (data->tag != NULL && !tag_is_empty(data->tag)) { - cmd = decoder_tag(data->decoder, data->input_stream, - data->tag); - tag_free(data->tag); - data->tag = tag_new(); - } else - cmd = decoder_get_command(decoder); - - if (cmd == DECODE_COMMAND_SEEK) { - FLAC__uint64 seek_sample = t_start + - decoder_seek_where(decoder) * - data->stream_info.sample_rate; - if (seek_sample >= t_start && - (t_end == 0 || seek_sample <= t_end) && - FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { - data->next_frame = seek_sample; - data->position = 0; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else if (cmd == DECODE_COMMAND_STOP || - FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) - break; - - if (t_end != 0 && data->next_frame >= t_end) - /* end of this sub track */ - break; - - if (!FLAC__stream_decoder_process_single(flac_dec)) { - cmd = decoder_get_command(decoder); - if (cmd != DECODE_COMMAND_SEEK) - break; - } - } - - if (cmd != DECODE_COMMAND_STOP) { - flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); - FLAC__stream_decoder_finish(flac_dec); - } -} - -static void -flac_decode_internal(struct decoder * decoder, - struct input_stream *input_stream, - bool is_ogg) -{ - FLAC__StreamDecoder *flac_dec; - struct flac_data data; - const char *err = NULL; - - flac_dec = flac_decoder_new(); - if (flac_dec == NULL) - return; - - flac_data_init(&data, decoder, input_stream); - data.tag = tag_new(); - - if (is_ogg) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - FLAC__StreamDecoderInitStatus status = - FLAC__stream_decoder_init_ogg_stream(flac_dec, - flac_read_cb, - flac_seek_cb, - flac_tell_cb, - flac_length_cb, - flac_eof_cb, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void *)&data); - if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - err = "doing Ogg init()"; - goto fail; - } -#else - goto fail; -#endif - } else { - FLAC__StreamDecoderInitStatus status = - FLAC__stream_decoder_init_stream(flac_dec, - flac_read_cb, - flac_seek_cb, - flac_tell_cb, - flac_length_cb, - flac_eof_cb, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void *)&data); - if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - err = "doing init()"; - goto fail; - } - } - - if (!flac_decoder_initialize(&data, flac_dec, - input_stream->seekable, 0)) { - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); - return; - } - - flac_decoder_loop(&data, flac_dec, 0, 0); - -fail: - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -static void -flac_decode(struct decoder * decoder, struct input_stream *input_stream) -{ - flac_decode_internal(decoder, input_stream, false); -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -/** - * @brief Decode a flac file with embedded cue sheets - * @param const char* fname filename on fs - */ -static void -flac_container_decode(struct decoder* decoder, - const char* fname, - bool is_ogg) -{ - unsigned int tnum = 0; - FLAC__uint64 t_start = 0; - FLAC__uint64 t_end = 0; - FLAC__uint64 track_time = 0; - FLAC__StreamMetadata* cs = NULL; - - FLAC__StreamDecoder *flac_dec; - FLAC__StreamDecoderInitStatus init_status; - struct flac_data data; - const char *err = NULL; - - char* pathname = g_strdup(fname); - char* slash = strrchr(pathname, '/'); - *slash = '\0'; - - tnum = flac_vtrack_tnum(fname); - - cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); - - FLAC__metadata_get_cuesheet(pathname, &cs); - - if (cs != NULL) - { - if (cs->data.cue_sheet.tracks != NULL - && (tnum <= cs->data.cue_sheet.num_tracks - 1)) - { - t_start = cs->data.cue_sheet.tracks[tnum - 1].offset; - t_end = cs->data.cue_sheet.tracks[tnum].offset; - track_time = cs->data.cue_sheet.tracks[tnum].offset - - cs->data.cue_sheet.tracks[tnum - 1].offset; - } - - FLAC__metadata_object_delete(cs); - } - else - { - g_free(pathname); - return; - } - - flac_dec = flac_decoder_new(); - if (flac_dec == NULL) - return; - - flac_data_init(&data, decoder, NULL); - - init_status = is_ogg - ? FLAC__stream_decoder_init_ogg_file(flac_dec, pathname, - flac_write_cb, - flacMetadata, - flac_error_cb, - &data) - : FLAC__stream_decoder_init_file(flac_dec, - pathname, flac_write_cb, - flacMetadata, flac_error_cb, - &data); - g_free(pathname); - if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - err = "doing init()"; - goto fail; - } - - if (!flac_decoder_initialize(&data, flac_dec, true, track_time)) { - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); - return; - } - - // seek to song start (order is important: after decoder init) - FLAC__stream_decoder_seek_absolute(flac_dec, (FLAC__uint64)t_start); - data.next_frame = t_start; - - flac_decoder_loop(&data, flac_dec, t_start, t_end); - -fail: - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -/** - * @brief Open a flac file for decoding - * @param const char* fname filename on fs - */ -static void -flac_filedecode_internal(struct decoder* decoder, - const char* fname, - bool is_ogg) -{ - FLAC__StreamDecoder *flac_dec; - struct flac_data data; - const char *err = NULL; - unsigned int flac_err_state = 0; - - flac_dec = flac_decoder_new(); - if (flac_dec == NULL) - return; - - flac_data_init(&data, decoder, NULL); - - if (is_ogg) - { - if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec, - fname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data )) - == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) - { - flac_container_decode(decoder, fname, is_ogg); - } - else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) - { - err = "doing Ogg init()"; - goto fail; - } - } - else - { - if ( (flac_err_state = FLAC__stream_decoder_init_file( flac_dec, - fname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data )) - == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) - { - flac_container_decode(decoder, fname, is_ogg); - } - else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) - { - err = "doing init()"; - goto fail; - } - } - - if (!flac_decoder_initialize(&data, flac_dec, true, 0)) { - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); - return; - } - - flac_decoder_loop(&data, flac_dec, 0, 0); - -fail: - flac_data_deinit(&data); - FLAC__stream_decoder_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -/** - * @brief wrapper function for - * flac_filedecode_internal method - * for decoding without ogg - */ -static void -flac_filedecode(struct decoder *decoder, const char *fname) -{ - struct stat st; - - if (stat(fname, &st) < 0) { - flac_container_decode(decoder, fname, false); - } else - flac_filedecode_internal(decoder, fname, false); -} - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -#ifndef HAVE_OGGFLAC - -static bool -oggflac_init(G_GNUC_UNUSED const struct config_param *param) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -static struct tag * -oggflac_tag_dup(const 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); - - ret = tag_new(); - do { - if (!(block = FLAC__metadata_iterator_get_block(it))) - break; - - flac_tag_apply_metadata(ret, NULL, block); - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); - - if (!tag_is_defined(ret)) { - tag_free(ret); - ret = NULL; - } - -out: - FLAC__metadata_chain_delete(chain); - return ret; -} - -static void -oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) -{ - if (ogg_stream_type_detect(input_stream) != FLAC) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_seek(input_stream, 0, SEEK_SET, NULL); - - flac_decode_internal(decoder, input_stream, true); -} - -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; -static const char *const oggflac_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/x-flac+ogg", - "audio/x-ogg", - NULL -}; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .tag_dup = oggflac_tag_dup, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif -}; - -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; -static const char *const flac_mime_types[] = { - "application/flac", - "application/x-flac", - "audio/flac", - "audio/x-flac", - NULL -}; - -const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .file_decode = flac_filedecode, -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - .tag_dup = flac_tag_dup, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .container_scan = flac_cue_track, -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ -}; diff --git a/src/decoder/fluidsynth_decoder_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c new file mode 100644 index 000000000..b9a2d0d99 --- /dev/null +++ b/src/decoder/fluidsynth_decoder_plugin.c @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * WARNING! This plugin suffers from major shortcomings in the + * libfluidsynth API, which render it practically unusable. For a + * discussion, see the post on the fluidsynth mailing list: + * + * http://www.mail-archive.com/fluid-dev@nongnu.org/msg01099.html + * + */ + +#include "config.h" +#include "decoder_api.h" +#include "timer.h" +#include "conf.h" + +#include + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "fluidsynth" + +/** + * Convert a fluidsynth log level to a GLib log level. + */ +static GLogLevelFlags +fluidsynth_level_to_glib(enum fluid_log_level level) +{ + switch (level) { + case FLUID_PANIC: + case FLUID_ERR: + return G_LOG_LEVEL_CRITICAL; + + case FLUID_WARN: + return G_LOG_LEVEL_WARNING; + + case FLUID_INFO: + return G_LOG_LEVEL_INFO; + + case FLUID_DBG: + case LAST_LOG_LEVEL: + return G_LOG_LEVEL_DEBUG; + } + + /* invalid fluidsynth log level */ + return G_LOG_LEVEL_MESSAGE; +} + +/** + * The fluidsynth logging callback. It forwards messages to the GLib + * logging library. + */ +static void +fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data) +{ + g_log(G_LOG_DOMAIN, fluidsynth_level_to_glib(level), "%s", message); +} + +static bool +fluidsynth_init(G_GNUC_UNUSED const struct config_param *param) +{ + fluid_set_log_function(LAST_LOG_LEVEL, + fluidsynth_mpd_log_function, NULL); + + return true; +} + +static void +fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) +{ + static const struct audio_format audio_format = { + .sample_rate = 48000, + .format = SAMPLE_FORMAT_S16, + .channels = 2, + }; + char setting_sample_rate[] = "synth.sample-rate"; + /* + char setting_verbose[] = "synth.verbose"; + char setting_yes[] = "yes"; + */ + const char *soundfont_path; + fluid_settings_t *settings; + fluid_synth_t *synth; + fluid_player_t *player; + char *path_dup; + int ret; + Timer *timer; + enum decoder_command cmd; + + soundfont_path = + config_get_string("soundfont", + "/usr/share/sounds/sf2/FluidR3_GM.sf2"); + + /* set up fluid settings */ + + settings = new_fluid_settings(); + if (settings == NULL) + return; + + fluid_settings_setnum(settings, setting_sample_rate, 48000); + + /* + fluid_settings_setstr(settings, setting_verbose, setting_yes); + */ + + /* create the fluid synth */ + + synth = new_fluid_synth(settings); + if (synth == NULL) { + delete_fluid_settings(settings); + return; + } + + ret = fluid_synth_sfload(synth, soundfont_path, true); + if (ret < 0) { + g_warning("fluid_synth_sfload() failed"); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* create the fluid player */ + + player = new_fluid_player(synth); + if (player == NULL) { + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* temporarily duplicate the path_fs string, because + fluidsynth wants a writable string */ + path_dup = g_strdup(path_fs); + ret = fluid_player_add(player, path_dup); + g_free(path_dup); + if (ret != 0) { + g_warning("fluid_player_add() failed"); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* start the player */ + + ret = fluid_player_play(player); + if (ret != 0) { + g_warning("fluid_player_play() failed"); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* set up a timer for synchronization; fluidsynth always + decodes in real time, which forces us to synchronize */ + /* XXX is there any way to switch off real-time decoding? */ + + timer = timer_new(&audio_format); + timer_start(timer); + + /* initialization complete - announce the audio format to the + MPD core */ + + decoder_initialized(decoder, &audio_format, false, -1); + + do { + int16_t buffer[2048]; + const unsigned max_frames = G_N_ELEMENTS(buffer) / 2; + + /* synchronize with the fluid player */ + + timer_add(timer, sizeof(buffer)); + timer_sync(timer); + + /* read samples from fluidsynth and send them to the + MPD core */ + + ret = fluid_synth_write_s16(synth, max_frames, + buffer, 0, 2, + buffer, 1, 2); + /* XXX how do we see whether the player is done? We + can't access the private attribute + player->status */ + if (ret != 0) + break; + + cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer), + 0); + } while (cmd == DECODE_COMMAND_NONE); + + /* clean up */ + + timer_free(timer); + + fluid_player_stop(player); + fluid_player_join(player); + + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); +} + +static struct tag * +fluidsynth_tag_dup(const char *file) +{ + struct tag *tag = tag_new(); + + /* to be implemented */ + (void)file; + + return tag; +} + +static const char *const fluidsynth_suffixes[] = { + "mid", + NULL +}; + +const struct decoder_plugin fluidsynth_decoder_plugin = { + .name = "fluidsynth", + .init = fluidsynth_init, + .file_decode = fluidsynth_file_decode, + .tag_dup = fluidsynth_tag_dup, + .suffixes = fluidsynth_suffixes, +}; diff --git a/src/decoder/fluidsynth_plugin.c b/src/decoder/fluidsynth_plugin.c deleted file mode 100644 index b9a2d0d99..000000000 --- a/src/decoder/fluidsynth_plugin.c +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * WARNING! This plugin suffers from major shortcomings in the - * libfluidsynth API, which render it practically unusable. For a - * discussion, see the post on the fluidsynth mailing list: - * - * http://www.mail-archive.com/fluid-dev@nongnu.org/msg01099.html - * - */ - -#include "config.h" -#include "decoder_api.h" -#include "timer.h" -#include "conf.h" - -#include - -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "fluidsynth" - -/** - * Convert a fluidsynth log level to a GLib log level. - */ -static GLogLevelFlags -fluidsynth_level_to_glib(enum fluid_log_level level) -{ - switch (level) { - case FLUID_PANIC: - case FLUID_ERR: - return G_LOG_LEVEL_CRITICAL; - - case FLUID_WARN: - return G_LOG_LEVEL_WARNING; - - case FLUID_INFO: - return G_LOG_LEVEL_INFO; - - case FLUID_DBG: - case LAST_LOG_LEVEL: - return G_LOG_LEVEL_DEBUG; - } - - /* invalid fluidsynth log level */ - return G_LOG_LEVEL_MESSAGE; -} - -/** - * The fluidsynth logging callback. It forwards messages to the GLib - * logging library. - */ -static void -fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data) -{ - g_log(G_LOG_DOMAIN, fluidsynth_level_to_glib(level), "%s", message); -} - -static bool -fluidsynth_init(G_GNUC_UNUSED const struct config_param *param) -{ - fluid_set_log_function(LAST_LOG_LEVEL, - fluidsynth_mpd_log_function, NULL); - - return true; -} - -static void -fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) -{ - static const struct audio_format audio_format = { - .sample_rate = 48000, - .format = SAMPLE_FORMAT_S16, - .channels = 2, - }; - char setting_sample_rate[] = "synth.sample-rate"; - /* - char setting_verbose[] = "synth.verbose"; - char setting_yes[] = "yes"; - */ - const char *soundfont_path; - fluid_settings_t *settings; - fluid_synth_t *synth; - fluid_player_t *player; - char *path_dup; - int ret; - Timer *timer; - enum decoder_command cmd; - - soundfont_path = - config_get_string("soundfont", - "/usr/share/sounds/sf2/FluidR3_GM.sf2"); - - /* set up fluid settings */ - - settings = new_fluid_settings(); - if (settings == NULL) - return; - - fluid_settings_setnum(settings, setting_sample_rate, 48000); - - /* - fluid_settings_setstr(settings, setting_verbose, setting_yes); - */ - - /* create the fluid synth */ - - synth = new_fluid_synth(settings); - if (synth == NULL) { - delete_fluid_settings(settings); - return; - } - - ret = fluid_synth_sfload(synth, soundfont_path, true); - if (ret < 0) { - g_warning("fluid_synth_sfload() failed"); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* create the fluid player */ - - player = new_fluid_player(synth); - if (player == NULL) { - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* temporarily duplicate the path_fs string, because - fluidsynth wants a writable string */ - path_dup = g_strdup(path_fs); - ret = fluid_player_add(player, path_dup); - g_free(path_dup); - if (ret != 0) { - g_warning("fluid_player_add() failed"); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* start the player */ - - ret = fluid_player_play(player); - if (ret != 0) { - g_warning("fluid_player_play() failed"); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* set up a timer for synchronization; fluidsynth always - decodes in real time, which forces us to synchronize */ - /* XXX is there any way to switch off real-time decoding? */ - - timer = timer_new(&audio_format); - timer_start(timer); - - /* initialization complete - announce the audio format to the - MPD core */ - - decoder_initialized(decoder, &audio_format, false, -1); - - do { - int16_t buffer[2048]; - const unsigned max_frames = G_N_ELEMENTS(buffer) / 2; - - /* synchronize with the fluid player */ - - timer_add(timer, sizeof(buffer)); - timer_sync(timer); - - /* read samples from fluidsynth and send them to the - MPD core */ - - ret = fluid_synth_write_s16(synth, max_frames, - buffer, 0, 2, - buffer, 1, 2); - /* XXX how do we see whether the player is done? We - can't access the private attribute - player->status */ - if (ret != 0) - break; - - cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer), - 0); - } while (cmd == DECODE_COMMAND_NONE); - - /* clean up */ - - timer_free(timer); - - fluid_player_stop(player); - fluid_player_join(player); - - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); -} - -static struct tag * -fluidsynth_tag_dup(const char *file) -{ - struct tag *tag = tag_new(); - - /* to be implemented */ - (void)file; - - return tag; -} - -static const char *const fluidsynth_suffixes[] = { - "mid", - NULL -}; - -const struct decoder_plugin fluidsynth_decoder_plugin = { - .name = "fluidsynth", - .init = fluidsynth_init, - .file_decode = fluidsynth_file_decode, - .tag_dup = fluidsynth_tag_dup, - .suffixes = fluidsynth_suffixes, -}; diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c new file mode 100644 index 000000000..87dfbeabd --- /dev/null +++ b/src/decoder/mad_decoder_plugin.c @@ -0,0 +1,1235 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "conf.h" +#include "tag_id3.h" +#include "audio_check.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ID3TAG +#include +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mad" + +#define FRAMES_CUSHION 2000 + +#define READ_BUFFER_SIZE 40960 + +enum mp3_action { + DECODE_SKIP = -3, + DECODE_BREAK = -2, + DECODE_CONT = -1, + DECODE_OK = 0 +}; + +enum muteframe { + MUTEFRAME_NONE, + MUTEFRAME_SKIP, + MUTEFRAME_SEEK +}; + +/* the number of samples of silence the decoder inserts at start */ +#define DECODERDELAY 529 + +#define DEFAULT_GAPLESS_MP3_PLAYBACK true + +static bool gapless_playback; + +static inline int32_t +mad_fixed_to_24_sample(mad_fixed_t sample) +{ + enum { + bits = 24, + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + /* round */ + sample = sample + (1L << (MAD_F_FRACBITS - bits)); + + /* clip */ + if (sample > MAX) + sample = MAX; + else if (sample < MIN) + sample = MIN; + + /* quantize */ + return sample >> (MAD_F_FRACBITS + 1 - bits); +} + +static void +mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, + unsigned int start, unsigned int end, + unsigned int num_channels) +{ + unsigned int i, c; + + for (i = start; i < end; ++i) { + for (c = 0; c < num_channels; ++c) + *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); + } +} + +static bool +mp3_plugin_init(G_GNUC_UNUSED const struct config_param *param) +{ + gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, + DEFAULT_GAPLESS_MP3_PLAYBACK); + return true; +} + +#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 + +struct mp3_data { + struct mad_stream stream; + struct mad_frame frame; + struct mad_synth synth; + mad_timer_t timer; + unsigned char input_buffer[READ_BUFFER_SIZE]; + int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; + float total_time; + float elapsed_time; + float seek_where; + enum muteframe mute_frame; + long *frame_offsets; + mad_timer_t *times; + unsigned long highest_frame; + unsigned long max_frames; + unsigned long current_frame; + unsigned int drop_start_frames; + unsigned int drop_end_frames; + unsigned int drop_start_samples; + unsigned int drop_end_samples; + bool found_replay_gain; + bool found_xing; + bool found_first_frame; + bool decoded_first_frame; + unsigned long bit_rate; + struct decoder *decoder; + struct input_stream *input_stream; + enum mad_layer layer; +}; + +static void +mp3_data_init(struct mp3_data *data, struct decoder *decoder, + struct input_stream *input_stream) +{ + data->mute_frame = MUTEFRAME_NONE; + data->highest_frame = 0; + data->max_frames = 0; + data->frame_offsets = NULL; + data->times = NULL; + data->current_frame = 0; + data->drop_start_frames = 0; + data->drop_end_frames = 0; + data->drop_start_samples = 0; + data->drop_end_samples = 0; + data->found_replay_gain = false; + data->found_xing = false; + data->found_first_frame = false; + data->decoded_first_frame = false; + data->decoder = decoder; + data->input_stream = input_stream; + data->layer = 0; + + mad_stream_init(&data->stream); + mad_stream_options(&data->stream, MAD_OPTION_IGNORECRC); + mad_frame_init(&data->frame); + mad_synth_init(&data->synth); + mad_timer_reset(&data->timer); +} + +static bool mp3_seek(struct mp3_data *data, long offset) +{ + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) + return false; + + mad_stream_buffer(&data->stream, data->input_buffer, 0); + (data->stream).error = 0; + + return true; +} + +static bool +mp3_fill_buffer(struct mp3_data *data) +{ + size_t remaining, length; + unsigned char *dest; + + if (data->stream.next_frame != NULL) { + remaining = data->stream.bufend - data->stream.next_frame; + memmove(data->input_buffer, data->stream.next_frame, + remaining); + dest = (data->input_buffer) + remaining; + length = READ_BUFFER_SIZE - remaining; + } else { + remaining = 0; + length = READ_BUFFER_SIZE; + dest = data->input_buffer; + } + + /* we've exhausted the read buffer, so give up!, these potential + * mp3 frames are way too big, and thus unlikely to be mp3 frames */ + if (length == 0) + return false; + + length = decoder_read(data->decoder, data->input_stream, dest, length); + if (length == 0) + return false; + + mad_stream_buffer(&data->stream, data->input_buffer, + length + remaining); + (data->stream).error = 0; + + return true; +} + +#ifdef HAVE_ID3TAG +/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */ +static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gain_info) +{ + struct id3_frame const * frame; + + id3_latin1_t const *id; + id3_byte_t const *data; + id3_length_t length; + int found; + + enum { + CHANNEL_OTHER = 0x00, + CHANNEL_MASTER_VOLUME = 0x01, + CHANNEL_FRONT_RIGHT = 0x02, + CHANNEL_FRONT_LEFT = 0x03, + CHANNEL_BACK_RIGHT = 0x04, + CHANNEL_BACK_LEFT = 0x05, + CHANNEL_FRONT_CENTRE = 0x06, + CHANNEL_BACK_CENTRE = 0x07, + CHANNEL_SUBWOOFER = 0x08 + }; + + found = 0; + + /* relative volume adjustment information */ + + frame = id3_tag_findframe(tag, "RVA2", 0); + if (!frame) return 0; + + id = id3_field_getlatin1(id3_frame_field(frame, 0)); + data = id3_field_getbinarydata(id3_frame_field(frame, 1), + &length); + + if (!id || !data) return 0; + + /* + * "The 'identification' string is used to identify the + * situation and/or device where this adjustment should apply. + * The following is then repeated for every channel + * + * Type of channel $xx + * Volume adjustment $xx xx + * Bits representing peak $xx + * Peak volume $xx (xx ...)" + */ + + while (length >= 4) { + unsigned int peak_bytes; + + peak_bytes = (data[3] + 7) / 8; + if (4 + peak_bytes > length) + break; + + if (data[0] == CHANNEL_MASTER_VOLUME) { + signed int voladj_fixed; + double voladj_float; + + /* + * "The volume adjustment is encoded as a fixed + * point decibel value, 16 bit signed integer + * representing (adjustment*512), giving +/- 64 + * dB with a precision of 0.001953125 dB." + */ + + voladj_fixed = (data[1] << 8) | (data[2] << 0); + voladj_fixed |= -(voladj_fixed & 0x8000); + + voladj_float = (double) voladj_fixed / 512; + + replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = voladj_float; + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = voladj_float; + + g_debug("parseRVA2: Relative Volume " + "%+.1f dB adjustment (%s)\n", + voladj_float, id); + + found = 1; + break; + } + + data += 4 + peak_bytes; + length -= 4 + peak_bytes; + } + + return found; +} +#endif + +#ifdef HAVE_ID3TAG +static struct replay_gain_info * +parse_id3_replay_gain_info(struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + bool found = false; + struct replay_gain_info *replay_gain_info; + + replay_gain_info = replay_gain_info_new(); + + for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = atof(value); + found = true; + } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value); + found = true; + } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = atof(value); + found = true; + } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) { + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value); + found = true; + } + + free(key); + free(value); + } + + if (!found) { + /* fall back on RVA2 if no replaygain tags found */ + found = parse_rva2(tag, replay_gain_info); + } + + if (found) + return replay_gain_info; + replay_gain_info_free(replay_gain_info); + return NULL; +} +#endif + +static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, + struct tag **mpd_tag) +{ +#ifdef HAVE_ID3TAG + struct id3_tag *id3_tag = NULL; + id3_length_t count; + id3_byte_t const *id3_data; + id3_byte_t *allocated = NULL; + + count = data->stream.bufend - data->stream.this_frame; + + if (tagsize <= count) { + id3_data = data->stream.this_frame; + mad_stream_skip(&(data->stream), tagsize); + } else { + allocated = g_malloc(tagsize); + memcpy(allocated, data->stream.this_frame, count); + mad_stream_skip(&(data->stream), count); + + while (count < tagsize) { + size_t len; + + len = decoder_read(data->decoder, data->input_stream, + allocated + count, tagsize - count); + if (len == 0) + break; + else + count += len; + } + + if (count != tagsize) { + g_debug("error parsing ID3 tag"); + g_free(allocated); + return; + } + + id3_data = allocated; + } + + id3_tag = id3_tag_parse(id3_data, tagsize); + if (id3_tag == NULL) { + g_free(allocated); + return; + } + + if (mpd_tag) { + struct tag *tmp_tag = tag_id3_import(id3_tag); + if (tmp_tag != NULL) { + if (*mpd_tag != NULL) + tag_free(*mpd_tag); + *mpd_tag = tmp_tag; + } + } + + if (data->decoder != NULL) { + struct replay_gain_info *tmp_rgi = + parse_id3_replay_gain_info(id3_tag); + if (tmp_rgi != NULL) { + decoder_replay_gain(data->decoder, tmp_rgi); + replay_gain_info_free(tmp_rgi); + data->found_replay_gain = true; + } + } + + id3_tag_delete(id3_tag); + + g_free(allocated); +#else /* !HAVE_ID3TAG */ + (void)mpd_tag; + + /* This code is enabled when libid3tag is disabled. Instead + of parsing the ID3 frame, it just skips it. */ + + mad_stream_skip(&data->stream, tagsize); +#endif +} + +#ifndef HAVE_ID3TAG +/** + * This function emulates libid3tag when it is disabled. Instead of + * doing a real analyzation of the frame, it just checks whether the + * frame begins with the string "ID3". If so, it returns the full + * length. + */ +static signed long +id3_tag_query(const void *p0, size_t length) +{ + const char *p = p0; + + return length > 3 && memcmp(p, "ID3", 3) == 0 + ? length + : 0; +} +#endif /* !HAVE_ID3TAG */ + +static enum mp3_action +decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag) +{ + enum mad_layer layer; + + if ((data->stream).buffer == NULL + || (data->stream).error == MAD_ERROR_BUFLEN) { + if (!mp3_fill_buffer(data)) + return DECODE_BREAK; + } + if (mad_header_decode(&data->frame.header, &data->stream)) { + if ((data->stream).error == MAD_ERROR_LOSTSYNC && + (data->stream).this_frame) { + signed long tagsize = id3_tag_query((data->stream). + this_frame, + (data->stream). + bufend - + (data->stream). + this_frame); + + if (tagsize > 0) { + if (tag && !(*tag)) { + mp3_parse_id3(data, (size_t)tagsize, + tag); + } else { + mad_stream_skip(&(data->stream), + tagsize); + } + return DECODE_CONT; + } + } + if (MAD_RECOVERABLE((data->stream).error)) { + return DECODE_SKIP; + } else { + if ((data->stream).error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + g_warning("unrecoverable frame level error " + "(%s).\n", + mad_stream_errorstr(&data->stream)); + return DECODE_BREAK; + } + } + } + + layer = data->frame.header.layer; + if (!data->layer) { + if (layer != MAD_LAYER_II && layer != MAD_LAYER_III) { + /* Only layer 2 and 3 have been tested to work */ + return DECODE_SKIP; + } + data->layer = layer; + } else if (layer != data->layer) { + /* Don't decode frames with a different layer than the first */ + return DECODE_SKIP; + } + + return DECODE_OK; +} + +static enum mp3_action +decodeNextFrame(struct mp3_data *data) +{ + if ((data->stream).buffer == NULL + || (data->stream).error == MAD_ERROR_BUFLEN) { + if (!mp3_fill_buffer(data)) + return DECODE_BREAK; + } + if (mad_frame_decode(&data->frame, &data->stream)) { + if ((data->stream).error == MAD_ERROR_LOSTSYNC) { + signed long tagsize = id3_tag_query((data->stream). + this_frame, + (data->stream). + bufend - + (data->stream). + this_frame); + if (tagsize > 0) { + mad_stream_skip(&(data->stream), tagsize); + return DECODE_CONT; + } + } + if (MAD_RECOVERABLE((data->stream).error)) { + return DECODE_SKIP; + } else { + if ((data->stream).error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + g_warning("unrecoverable frame level error " + "(%s).\n", + mad_stream_errorstr(&data->stream)); + return DECODE_BREAK; + } + } + } + + return DECODE_OK; +} + +/* xing stuff stolen from alsaplayer, and heavily modified by jat */ +#define XI_MAGIC (('X' << 8) | 'i') +#define NG_MAGIC (('n' << 8) | 'g') +#define IN_MAGIC (('I' << 8) | 'n') +#define FO_MAGIC (('f' << 8) | 'o') + +enum xing_magic { + XING_MAGIC_XING, /* VBR */ + XING_MAGIC_INFO /* CBR */ +}; + +struct xing { + long flags; /* valid fields (see below) */ + unsigned long frames; /* total number of frames */ + unsigned long bytes; /* total number of bytes */ + unsigned char toc[100]; /* 100-point seek table */ + long scale; /* VBR quality */ + enum xing_magic magic; /* header magic */ +}; + +enum { + XING_FRAMES = 0x00000001L, + XING_BYTES = 0x00000002L, + XING_TOC = 0x00000004L, + XING_SCALE = 0x00000008L +}; + +struct version { + unsigned major; + unsigned minor; +}; + +struct lame { + char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ + struct version version; /* struct containing just the version */ + float peak; /* replaygain peak */ + float track_gain; /* replaygain track gain */ + float album_gain; /* replaygain album gain */ + int encoder_delay; /* # of added samples at start of mp3 */ + int encoder_padding; /* # of added samples at end of mp3 */ + int crc; /* CRC of the first 190 bytes of this frame */ +}; + +static bool +parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) +{ + unsigned long bits; + int bitlen; + int bitsleft; + int i; + + bitlen = *oldbitlen; + + if (bitlen < 16) + return false; + + bits = mad_bit_read(ptr, 16); + bitlen -= 16; + + if (bits == XI_MAGIC) { + if (bitlen < 16) + return false; + + if (mad_bit_read(ptr, 16) != NG_MAGIC) + return false; + + bitlen -= 16; + xing->magic = XING_MAGIC_XING; + } else if (bits == IN_MAGIC) { + if (bitlen < 16) + return false; + + if (mad_bit_read(ptr, 16) != FO_MAGIC) + return false; + + bitlen -= 16; + xing->magic = XING_MAGIC_INFO; + } + else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; + else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; + else + return false; + + if (bitlen < 32) + return false; + xing->flags = mad_bit_read(ptr, 32); + bitlen -= 32; + + if (xing->flags & XING_FRAMES) { + if (bitlen < 32) + return false; + xing->frames = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_BYTES) { + if (bitlen < 32) + return false; + xing->bytes = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_TOC) { + if (bitlen < 800) + return false; + for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); + bitlen -= 800; + } + + if (xing->flags & XING_SCALE) { + if (bitlen < 32) + return false; + xing->scale = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + /* Make sure we consume no less than 120 bytes (960 bits) in hopes that + * the LAME tag is found there, and not right after the Xing header */ + bitsleft = 960 - ((*oldbitlen) - bitlen); + if (bitsleft < 0) + return false; + else if (bitsleft > 0) { + mad_bit_read(ptr, bitsleft); + bitlen -= bitsleft; + } + + *oldbitlen = bitlen; + + return true; +} + +static bool +parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) +{ + int adj = 0; + int name; + int orig; + int sign; + int gain; + int i; + + /* Unlike the xing header, the lame tag has a fixed length. Fail if + * not all 36 bytes (288 bits) are there. */ + if (*bitlen < 288) + return false; + + for (i = 0; i < 9; i++) + lame->encoder[i] = (char)mad_bit_read(ptr, 8); + lame->encoder[9] = '\0'; + + *bitlen -= 72; + + /* This is technically incorrect, since the encoder might not be lame. + * But there's no other way to determine if this is a lame tag, and we + * wouldn't want to go reading a tag that's not there. */ + if (!g_str_has_prefix(lame->encoder, "LAME")) + return false; + + if (sscanf(lame->encoder+4, "%u.%u", + &lame->version.major, &lame->version.minor) != 2) + return false; + + g_debug("detected LAME version %i.%i (\"%s\")\n", + lame->version.major, lame->version.minor, lame->encoder); + + /* The reference volume was changed from the 83dB used in the + * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older + * versions, since everyone else uses 89dB instead of 83dB. + * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so + * it's impossible to make the proper adjustment for 3.95. + * Fortunately, 3.95 was only out for about a day before 3.95.1 was + * released. -- tmz */ + if (lame->version.major < 3 || + (lame->version.major == 3 && lame->version.minor < 95)) + adj = 6; + + mad_bit_read(ptr, 16); + + lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ + g_debug("LAME peak found: %f\n", lame->peak); + + lame->track_gain = 0; + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 1 && orig != 0) { + lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; + g_debug("LAME track gain found: %f\n", lame->track_gain); + } + + /* tmz reports that this isn't currently written by any version of lame + * (as of 3.97). Since we have no way of testing it, don't use it. + * Wouldn't want to go blowing someone's ears just because we read it + * wrong. :P -- jat */ + lame->album_gain = 0; +#if 0 + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 2 && orig != 0) { + lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj; + g_debug("LAME album gain found: %f\n", lame->track_gain); + } +#else + mad_bit_read(ptr, 16); +#endif + + mad_bit_read(ptr, 16); + + lame->encoder_delay = mad_bit_read(ptr, 12); + lame->encoder_padding = mad_bit_read(ptr, 12); + + g_debug("encoder delay is %i, encoder padding is %i\n", + lame->encoder_delay, lame->encoder_padding); + + mad_bit_read(ptr, 80); + + lame->crc = mad_bit_read(ptr, 16); + + *bitlen -= 216; + + return true; +} + +static inline float +mp3_frame_duration(const struct mad_frame *frame) +{ + return mad_timer_count(frame->header.duration, + MAD_UNITS_MILLISECONDS) / 1000.0; +} + +static goffset +mp3_this_frame_offset(const struct mp3_data *data) +{ + goffset offset = data->input_stream->offset; + + if (data->stream.this_frame != NULL) + offset -= data->stream.bufend - data->stream.this_frame; + else + offset -= data->stream.bufend - data->stream.buffer; + + return offset; +} + +static goffset +mp3_rest_including_this_frame(const struct mp3_data *data) +{ + return data->input_stream->size - mp3_this_frame_offset(data); +} + +/** + * Attempt to calulcate the length of the song from filesize + */ +static void +mp3_filesize_to_song_length(struct mp3_data *data) +{ + goffset rest = mp3_rest_including_this_frame(data); + + if (rest > 0) { + float frame_duration = mp3_frame_duration(&data->frame); + + data->total_time = (rest * 8.0) / (data->frame).header.bitrate; + data->max_frames = data->total_time / frame_duration + + FRAMES_CUSHION; + } else { + data->max_frames = FRAMES_CUSHION; + data->total_time = 0; + } +} + +static bool +mp3_decode_first_frame(struct mp3_data *data, struct tag **tag) +{ + struct xing xing; + struct lame lame; + struct mad_bitptr ptr; + int bitlen; + enum mp3_action ret; + + /* stfu gcc */ + memset(&xing, 0, sizeof(struct xing)); + xing.flags = 0; + + while (true) { + do { + ret = decode_next_frame_header(data, tag); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_SKIP) continue; + + do { + ret = decodeNextFrame(data); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_OK) break; + } + + ptr = data->stream.anc_ptr; + bitlen = data->stream.anc_bitlen; + + mp3_filesize_to_song_length(data); + + /* + * if an xing tag exists, use that! + */ + if (parse_xing(&xing, &ptr, &bitlen)) { + data->found_xing = true; + data->mute_frame = MUTEFRAME_SKIP; + + if ((xing.flags & XING_FRAMES) && xing.frames) { + mad_timer_t duration = data->frame.header.duration; + mad_timer_multiply(&duration, xing.frames); + data->total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; + data->max_frames = xing.frames; + } + + if (parse_lame(&lame, &ptr, &bitlen)) { + if (gapless_playback && + data->input_stream->seekable) { + data->drop_start_samples = lame.encoder_delay + + DECODERDELAY; + data->drop_end_samples = lame.encoder_padding; + } + + /* Album gain isn't currently used. See comment in + * parse_lame() for details. -- jat */ + if (data->decoder != NULL && + !data->found_replay_gain && + lame.track_gain) { + struct replay_gain_info *rgi + = replay_gain_info_new(); + rgi->tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; + rgi->tuples[REPLAY_GAIN_TRACK].peak = lame.peak; + decoder_replay_gain(data->decoder, rgi); + replay_gain_info_free(rgi); + } + } + } + + if (!data->max_frames) + return false; + + if (data->max_frames > 8 * 1024 * 1024) { + g_warning("mp3 file header indicates too many frames: %lu\n", + data->max_frames); + return false; + } + + data->frame_offsets = g_malloc(sizeof(long) * data->max_frames); + data->times = g_malloc(sizeof(mad_timer_t) * data->max_frames); + + return true; +} + +static void mp3_data_finish(struct mp3_data *data) +{ + mad_synth_finish(&data->synth); + mad_frame_finish(&data->frame); + mad_stream_finish(&data->stream); + + g_free(data->frame_offsets); + g_free(data->times); +} + +/* this is primarily used for getting total time for tags */ +static int +mad_decoder_total_file_time(struct input_stream *is) +{ + struct mp3_data data; + int ret; + + mp3_data_init(&data, NULL, is); + if (!mp3_decode_first_frame(&data, NULL)) + ret = -1; + else + ret = data.total_time + 0.5; + mp3_data_finish(&data); + + return ret; +} + +static bool +mp3_open(struct input_stream *is, struct mp3_data *data, + struct decoder *decoder, struct tag **tag) +{ + mp3_data_init(data, decoder, is); + *tag = NULL; + if (!mp3_decode_first_frame(data, tag)) { + mp3_data_finish(data); + if (tag && *tag) + tag_free(*tag); + return false; + } + + return true; +} + +static long +mp3_time_to_frame(const struct mp3_data *data, double t) +{ + unsigned long i; + + for (i = 0; i < data->highest_frame; ++i) { + double frame_time = + mad_timer_count(data->times[i], + MAD_UNITS_MILLISECONDS) / 1000.; + if (frame_time >= t) + break; + } + + return i; +} + +static void +mp3_update_timer_next_frame(struct mp3_data *data) +{ + if (data->current_frame >= data->highest_frame) { + /* record this frame's properties in + data->frame_offsets (for seeking) and + data->times */ + data->bit_rate = (data->frame).header.bitrate; + + if (data->current_frame >= data->max_frames) + /* cap data->current_frame */ + data->current_frame = data->max_frames - 1; + else + data->highest_frame++; + + data->frame_offsets[data->current_frame] = + mp3_this_frame_offset(data); + + mad_timer_add(&data->timer, (data->frame).header.duration); + data->times[data->current_frame] = data->timer; + } else + /* get the new timer value from data->times */ + data->timer = data->times[data->current_frame]; + + data->current_frame++; + data->elapsed_time = + mad_timer_count(data->timer, MAD_UNITS_MILLISECONDS) / 1000.0; +} + +/** + * Sends the synthesized current frame via decoder_data(). + */ +static enum decoder_command +mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length) +{ + unsigned max_samples; + + max_samples = sizeof(data->output_buffer) / + sizeof(data->output_buffer[0]) / + MAD_NCHANNELS(&(data->frame).header); + + while (i < pcm_length) { + enum decoder_command cmd; + unsigned int num_samples = pcm_length - i; + if (num_samples > max_samples) + num_samples = max_samples; + + i += num_samples; + + mad_fixed_to_24_buffer(data->output_buffer, + &data->synth, + i - num_samples, i, + MAD_NCHANNELS(&(data->frame).header)); + num_samples *= MAD_NCHANNELS(&(data->frame).header); + + cmd = decoder_data(data->decoder, data->input_stream, + data->output_buffer, + sizeof(data->output_buffer[0]) * num_samples, + data->bit_rate / 1000); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +/** + * Synthesize the current frame and send it via decoder_data(). + */ +static enum decoder_command +mp3_synth_and_send(struct mp3_data *data) +{ + unsigned i, pcm_length; + enum decoder_command cmd; + + mad_synth_frame(&data->synth, &data->frame); + + if (!data->found_first_frame) { + unsigned int samples_per_frame = data->synth.pcm.length; + data->drop_start_frames = data->drop_start_samples / samples_per_frame; + data->drop_end_frames = data->drop_end_samples / samples_per_frame; + data->drop_start_samples = data->drop_start_samples % samples_per_frame; + data->drop_end_samples = data->drop_end_samples % samples_per_frame; + data->found_first_frame = true; + } + + if (data->drop_start_frames > 0) { + data->drop_start_frames--; + return DECODE_COMMAND_NONE; + } else if ((data->drop_end_frames > 0) && + (data->current_frame == (data->max_frames + 1 - data->drop_end_frames))) { + /* stop decoding, effectively dropping all remaining + frames */ + return DECODE_COMMAND_STOP; + } + + if (!data->decoded_first_frame) { + i = data->drop_start_samples; + data->decoded_first_frame = true; + } else + i = 0; + + pcm_length = data->synth.pcm.length; + if (data->drop_end_samples && + (data->current_frame == data->max_frames - data->drop_end_frames)) { + if (data->drop_end_samples >= pcm_length) + pcm_length = 0; + else + pcm_length -= data->drop_end_samples; + } + + cmd = mp3_send_pcm(data, i, pcm_length); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + + if (data->drop_end_samples && + (data->current_frame == data->max_frames - data->drop_end_frames)) + /* stop decoding, effectively dropping + * all remaining samples */ + return DECODE_COMMAND_STOP; + + return DECODE_COMMAND_NONE; +} + +static bool +mp3_read(struct mp3_data *data) +{ + struct decoder *decoder = data->decoder; + enum mp3_action ret; + enum decoder_command cmd; + + mp3_update_timer_next_frame(data); + + switch (data->mute_frame) { + case MUTEFRAME_SKIP: + data->mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_SEEK: + if (data->elapsed_time >= data->seek_where) + data->mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_NONE: + cmd = mp3_synth_and_send(data); + if (cmd == DECODE_COMMAND_SEEK) { + unsigned long j; + + assert(data->input_stream->seekable); + + j = mp3_time_to_frame(data, + decoder_seek_where(decoder)); + if (j < data->highest_frame) { + if (mp3_seek(data, data->frame_offsets[j])) { + data->current_frame = j; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else { + data->seek_where = decoder_seek_where(decoder); + data->mute_frame = MUTEFRAME_SEEK; + decoder_command_finished(decoder); + } + } else if (cmd != DECODE_COMMAND_NONE) + return false; + } + + while (true) { + bool skip = false; + + do { + struct tag *tag = NULL; + + ret = decode_next_frame_header(data, &tag); + + if (tag != NULL) { + decoder_tag(decoder, data->input_stream, tag); + tag_free(tag); + } + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + else if (ret == DECODE_SKIP) + skip = true; + + if (data->mute_frame == MUTEFRAME_NONE) { + do { + ret = decodeNextFrame(data); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + } + + if (!skip && ret == DECODE_OK) + break; + } + + return ret != DECODE_BREAK; +} + +static void +mp3_decode(struct decoder *decoder, struct input_stream *input_stream) +{ + struct mp3_data data; + GError *error = NULL; + struct tag *tag = NULL; + struct audio_format audio_format; + + if (!mp3_open(input_stream, &data, decoder, &tag)) { + if (decoder_get_command(decoder) == DECODE_COMMAND_NONE) + g_warning + ("Input does not appear to be a mp3 bit stream.\n"); + return; + } + + if (!audio_format_init_checked(&audio_format, + data.frame.header.samplerate, + SAMPLE_FORMAT_S24_P32, + MAD_NCHANNELS(&data.frame.header), + &error)) { + g_warning("%s", error->message); + g_error_free(error); + + if (tag != NULL) + tag_free(tag); + mp3_data_finish(&data); + return; + } + + decoder_initialized(decoder, &audio_format, + data.input_stream->seekable, data.total_time); + + if (tag != NULL) { + decoder_tag(decoder, input_stream, tag); + tag_free(tag); + } + + while (mp3_read(&data)) ; + + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && + data.mute_frame == MUTEFRAME_SEEK) + decoder_command_finished(decoder); + + mp3_data_finish(&data); +} + +static struct tag * +mad_decoder_stream_tag(struct input_stream *is) +{ + struct tag *tag; + int total_time; + + total_time = mad_decoder_total_file_time(is); + if (total_time < 0) + return NULL; + + tag = tag_new(); + tag->time = total_time; + return tag; +} + +static const char *const mp3_suffixes[] = { "mp3", "mp2", NULL }; +static const char *const mp3_mime_types[] = { "audio/mpeg", NULL }; + +const struct decoder_plugin mad_decoder_plugin = { + .name = "mad", + .init = mp3_plugin_init, + .stream_decode = mp3_decode, + .stream_tag = mad_decoder_stream_tag, + .suffixes = mp3_suffixes, + .mime_types = mp3_mime_types +}; diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c deleted file mode 100644 index 87dfbeabd..000000000 --- a/src/decoder/mad_plugin.c +++ /dev/null @@ -1,1235 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "conf.h" -#include "tag_id3.h" -#include "audio_check.h" - -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_ID3TAG -#include -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mad" - -#define FRAMES_CUSHION 2000 - -#define READ_BUFFER_SIZE 40960 - -enum mp3_action { - DECODE_SKIP = -3, - DECODE_BREAK = -2, - DECODE_CONT = -1, - DECODE_OK = 0 -}; - -enum muteframe { - MUTEFRAME_NONE, - MUTEFRAME_SKIP, - MUTEFRAME_SEEK -}; - -/* the number of samples of silence the decoder inserts at start */ -#define DECODERDELAY 529 - -#define DEFAULT_GAPLESS_MP3_PLAYBACK true - -static bool gapless_playback; - -static inline int32_t -mad_fixed_to_24_sample(mad_fixed_t sample) -{ - enum { - bits = 24, - MIN = -MAD_F_ONE, - MAX = MAD_F_ONE - 1 - }; - - /* round */ - sample = sample + (1L << (MAD_F_FRACBITS - bits)); - - /* clip */ - if (sample > MAX) - sample = MAX; - else if (sample < MIN) - sample = MIN; - - /* quantize */ - return sample >> (MAD_F_FRACBITS + 1 - bits); -} - -static void -mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, - unsigned int start, unsigned int end, - unsigned int num_channels) -{ - unsigned int i, c; - - for (i = start; i < end; ++i) { - for (c = 0; c < num_channels; ++c) - *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); - } -} - -static bool -mp3_plugin_init(G_GNUC_UNUSED const struct config_param *param) -{ - gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, - DEFAULT_GAPLESS_MP3_PLAYBACK); - return true; -} - -#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 - -struct mp3_data { - struct mad_stream stream; - struct mad_frame frame; - struct mad_synth synth; - mad_timer_t timer; - unsigned char input_buffer[READ_BUFFER_SIZE]; - int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; - float total_time; - float elapsed_time; - float seek_where; - enum muteframe mute_frame; - long *frame_offsets; - mad_timer_t *times; - unsigned long highest_frame; - unsigned long max_frames; - unsigned long current_frame; - unsigned int drop_start_frames; - unsigned int drop_end_frames; - unsigned int drop_start_samples; - unsigned int drop_end_samples; - bool found_replay_gain; - bool found_xing; - bool found_first_frame; - bool decoded_first_frame; - unsigned long bit_rate; - struct decoder *decoder; - struct input_stream *input_stream; - enum mad_layer layer; -}; - -static void -mp3_data_init(struct mp3_data *data, struct decoder *decoder, - struct input_stream *input_stream) -{ - data->mute_frame = MUTEFRAME_NONE; - data->highest_frame = 0; - data->max_frames = 0; - data->frame_offsets = NULL; - data->times = NULL; - data->current_frame = 0; - data->drop_start_frames = 0; - data->drop_end_frames = 0; - data->drop_start_samples = 0; - data->drop_end_samples = 0; - data->found_replay_gain = false; - data->found_xing = false; - data->found_first_frame = false; - data->decoded_first_frame = false; - data->decoder = decoder; - data->input_stream = input_stream; - data->layer = 0; - - mad_stream_init(&data->stream); - mad_stream_options(&data->stream, MAD_OPTION_IGNORECRC); - mad_frame_init(&data->frame); - mad_synth_init(&data->synth); - mad_timer_reset(&data->timer); -} - -static bool mp3_seek(struct mp3_data *data, long offset) -{ - if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) - return false; - - mad_stream_buffer(&data->stream, data->input_buffer, 0); - (data->stream).error = 0; - - return true; -} - -static bool -mp3_fill_buffer(struct mp3_data *data) -{ - size_t remaining, length; - unsigned char *dest; - - if (data->stream.next_frame != NULL) { - remaining = data->stream.bufend - data->stream.next_frame; - memmove(data->input_buffer, data->stream.next_frame, - remaining); - dest = (data->input_buffer) + remaining; - length = READ_BUFFER_SIZE - remaining; - } else { - remaining = 0; - length = READ_BUFFER_SIZE; - dest = data->input_buffer; - } - - /* we've exhausted the read buffer, so give up!, these potential - * mp3 frames are way too big, and thus unlikely to be mp3 frames */ - if (length == 0) - return false; - - length = decoder_read(data->decoder, data->input_stream, dest, length); - if (length == 0) - return false; - - mad_stream_buffer(&data->stream, data->input_buffer, - length + remaining); - (data->stream).error = 0; - - return true; -} - -#ifdef HAVE_ID3TAG -/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */ -static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gain_info) -{ - struct id3_frame const * frame; - - id3_latin1_t const *id; - id3_byte_t const *data; - id3_length_t length; - int found; - - enum { - CHANNEL_OTHER = 0x00, - CHANNEL_MASTER_VOLUME = 0x01, - CHANNEL_FRONT_RIGHT = 0x02, - CHANNEL_FRONT_LEFT = 0x03, - CHANNEL_BACK_RIGHT = 0x04, - CHANNEL_BACK_LEFT = 0x05, - CHANNEL_FRONT_CENTRE = 0x06, - CHANNEL_BACK_CENTRE = 0x07, - CHANNEL_SUBWOOFER = 0x08 - }; - - found = 0; - - /* relative volume adjustment information */ - - frame = id3_tag_findframe(tag, "RVA2", 0); - if (!frame) return 0; - - id = id3_field_getlatin1(id3_frame_field(frame, 0)); - data = id3_field_getbinarydata(id3_frame_field(frame, 1), - &length); - - if (!id || !data) return 0; - - /* - * "The 'identification' string is used to identify the - * situation and/or device where this adjustment should apply. - * The following is then repeated for every channel - * - * Type of channel $xx - * Volume adjustment $xx xx - * Bits representing peak $xx - * Peak volume $xx (xx ...)" - */ - - while (length >= 4) { - unsigned int peak_bytes; - - peak_bytes = (data[3] + 7) / 8; - if (4 + peak_bytes > length) - break; - - if (data[0] == CHANNEL_MASTER_VOLUME) { - signed int voladj_fixed; - double voladj_float; - - /* - * "The volume adjustment is encoded as a fixed - * point decibel value, 16 bit signed integer - * representing (adjustment*512), giving +/- 64 - * dB with a precision of 0.001953125 dB." - */ - - voladj_fixed = (data[1] << 8) | (data[2] << 0); - voladj_fixed |= -(voladj_fixed & 0x8000); - - voladj_float = (double) voladj_fixed / 512; - - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = voladj_float; - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = voladj_float; - - g_debug("parseRVA2: Relative Volume " - "%+.1f dB adjustment (%s)\n", - voladj_float, id); - - found = 1; - break; - } - - data += 4 + peak_bytes; - length -= 4 + peak_bytes; - } - - return found; -} -#endif - -#ifdef HAVE_ID3TAG -static struct replay_gain_info * -parse_id3_replay_gain_info(struct id3_tag *tag) -{ - int i; - char *key; - char *value; - struct id3_frame *frame; - bool found = false; - struct replay_gain_info *replay_gain_info; - - replay_gain_info = replay_gain_info_new(); - - for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { - if (frame->nfields < 3) - continue; - - key = (char *) - id3_ucs4_latin1duplicate(id3_field_getstring - (&frame->fields[1])); - value = (char *) - id3_ucs4_latin1duplicate(id3_field_getstring - (&frame->fields[2])); - - if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = atof(value); - found = true; - } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value); - found = true; - } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = atof(value); - found = true; - } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) { - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value); - found = true; - } - - free(key); - free(value); - } - - if (!found) { - /* fall back on RVA2 if no replaygain tags found */ - found = parse_rva2(tag, replay_gain_info); - } - - if (found) - return replay_gain_info; - replay_gain_info_free(replay_gain_info); - return NULL; -} -#endif - -static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, - struct tag **mpd_tag) -{ -#ifdef HAVE_ID3TAG - struct id3_tag *id3_tag = NULL; - id3_length_t count; - id3_byte_t const *id3_data; - id3_byte_t *allocated = NULL; - - count = data->stream.bufend - data->stream.this_frame; - - if (tagsize <= count) { - id3_data = data->stream.this_frame; - mad_stream_skip(&(data->stream), tagsize); - } else { - allocated = g_malloc(tagsize); - memcpy(allocated, data->stream.this_frame, count); - mad_stream_skip(&(data->stream), count); - - while (count < tagsize) { - size_t len; - - len = decoder_read(data->decoder, data->input_stream, - allocated + count, tagsize - count); - if (len == 0) - break; - else - count += len; - } - - if (count != tagsize) { - g_debug("error parsing ID3 tag"); - g_free(allocated); - return; - } - - id3_data = allocated; - } - - id3_tag = id3_tag_parse(id3_data, tagsize); - if (id3_tag == NULL) { - g_free(allocated); - return; - } - - if (mpd_tag) { - struct tag *tmp_tag = tag_id3_import(id3_tag); - if (tmp_tag != NULL) { - if (*mpd_tag != NULL) - tag_free(*mpd_tag); - *mpd_tag = tmp_tag; - } - } - - if (data->decoder != NULL) { - struct replay_gain_info *tmp_rgi = - parse_id3_replay_gain_info(id3_tag); - if (tmp_rgi != NULL) { - decoder_replay_gain(data->decoder, tmp_rgi); - replay_gain_info_free(tmp_rgi); - data->found_replay_gain = true; - } - } - - id3_tag_delete(id3_tag); - - g_free(allocated); -#else /* !HAVE_ID3TAG */ - (void)mpd_tag; - - /* This code is enabled when libid3tag is disabled. Instead - of parsing the ID3 frame, it just skips it. */ - - mad_stream_skip(&data->stream, tagsize); -#endif -} - -#ifndef HAVE_ID3TAG -/** - * This function emulates libid3tag when it is disabled. Instead of - * doing a real analyzation of the frame, it just checks whether the - * frame begins with the string "ID3". If so, it returns the full - * length. - */ -static signed long -id3_tag_query(const void *p0, size_t length) -{ - const char *p = p0; - - return length > 3 && memcmp(p, "ID3", 3) == 0 - ? length - : 0; -} -#endif /* !HAVE_ID3TAG */ - -static enum mp3_action -decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag) -{ - enum mad_layer layer; - - if ((data->stream).buffer == NULL - || (data->stream).error == MAD_ERROR_BUFLEN) { - if (!mp3_fill_buffer(data)) - return DECODE_BREAK; - } - if (mad_header_decode(&data->frame.header, &data->stream)) { - if ((data->stream).error == MAD_ERROR_LOSTSYNC && - (data->stream).this_frame) { - signed long tagsize = id3_tag_query((data->stream). - this_frame, - (data->stream). - bufend - - (data->stream). - this_frame); - - if (tagsize > 0) { - if (tag && !(*tag)) { - mp3_parse_id3(data, (size_t)tagsize, - tag); - } else { - mad_stream_skip(&(data->stream), - tagsize); - } - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE((data->stream).error)) { - return DECODE_SKIP; - } else { - if ((data->stream).error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - g_warning("unrecoverable frame level error " - "(%s).\n", - mad_stream_errorstr(&data->stream)); - return DECODE_BREAK; - } - } - } - - layer = data->frame.header.layer; - if (!data->layer) { - if (layer != MAD_LAYER_II && layer != MAD_LAYER_III) { - /* Only layer 2 and 3 have been tested to work */ - return DECODE_SKIP; - } - data->layer = layer; - } else if (layer != data->layer) { - /* Don't decode frames with a different layer than the first */ - return DECODE_SKIP; - } - - return DECODE_OK; -} - -static enum mp3_action -decodeNextFrame(struct mp3_data *data) -{ - if ((data->stream).buffer == NULL - || (data->stream).error == MAD_ERROR_BUFLEN) { - if (!mp3_fill_buffer(data)) - return DECODE_BREAK; - } - if (mad_frame_decode(&data->frame, &data->stream)) { - if ((data->stream).error == MAD_ERROR_LOSTSYNC) { - signed long tagsize = id3_tag_query((data->stream). - this_frame, - (data->stream). - bufend - - (data->stream). - this_frame); - if (tagsize > 0) { - mad_stream_skip(&(data->stream), tagsize); - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE((data->stream).error)) { - return DECODE_SKIP; - } else { - if ((data->stream).error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - g_warning("unrecoverable frame level error " - "(%s).\n", - mad_stream_errorstr(&data->stream)); - return DECODE_BREAK; - } - } - } - - return DECODE_OK; -} - -/* xing stuff stolen from alsaplayer, and heavily modified by jat */ -#define XI_MAGIC (('X' << 8) | 'i') -#define NG_MAGIC (('n' << 8) | 'g') -#define IN_MAGIC (('I' << 8) | 'n') -#define FO_MAGIC (('f' << 8) | 'o') - -enum xing_magic { - XING_MAGIC_XING, /* VBR */ - XING_MAGIC_INFO /* CBR */ -}; - -struct xing { - long flags; /* valid fields (see below) */ - unsigned long frames; /* total number of frames */ - unsigned long bytes; /* total number of bytes */ - unsigned char toc[100]; /* 100-point seek table */ - long scale; /* VBR quality */ - enum xing_magic magic; /* header magic */ -}; - -enum { - XING_FRAMES = 0x00000001L, - XING_BYTES = 0x00000002L, - XING_TOC = 0x00000004L, - XING_SCALE = 0x00000008L -}; - -struct version { - unsigned major; - unsigned minor; -}; - -struct lame { - char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ - struct version version; /* struct containing just the version */ - float peak; /* replaygain peak */ - float track_gain; /* replaygain track gain */ - float album_gain; /* replaygain album gain */ - int encoder_delay; /* # of added samples at start of mp3 */ - int encoder_padding; /* # of added samples at end of mp3 */ - int crc; /* CRC of the first 190 bytes of this frame */ -}; - -static bool -parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) -{ - unsigned long bits; - int bitlen; - int bitsleft; - int i; - - bitlen = *oldbitlen; - - if (bitlen < 16) - return false; - - bits = mad_bit_read(ptr, 16); - bitlen -= 16; - - if (bits == XI_MAGIC) { - if (bitlen < 16) - return false; - - if (mad_bit_read(ptr, 16) != NG_MAGIC) - return false; - - bitlen -= 16; - xing->magic = XING_MAGIC_XING; - } else if (bits == IN_MAGIC) { - if (bitlen < 16) - return false; - - if (mad_bit_read(ptr, 16) != FO_MAGIC) - return false; - - bitlen -= 16; - xing->magic = XING_MAGIC_INFO; - } - else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; - else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; - else - return false; - - if (bitlen < 32) - return false; - xing->flags = mad_bit_read(ptr, 32); - bitlen -= 32; - - if (xing->flags & XING_FRAMES) { - if (bitlen < 32) - return false; - xing->frames = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - if (xing->flags & XING_BYTES) { - if (bitlen < 32) - return false; - xing->bytes = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - if (xing->flags & XING_TOC) { - if (bitlen < 800) - return false; - for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); - bitlen -= 800; - } - - if (xing->flags & XING_SCALE) { - if (bitlen < 32) - return false; - xing->scale = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - /* Make sure we consume no less than 120 bytes (960 bits) in hopes that - * the LAME tag is found there, and not right after the Xing header */ - bitsleft = 960 - ((*oldbitlen) - bitlen); - if (bitsleft < 0) - return false; - else if (bitsleft > 0) { - mad_bit_read(ptr, bitsleft); - bitlen -= bitsleft; - } - - *oldbitlen = bitlen; - - return true; -} - -static bool -parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) -{ - int adj = 0; - int name; - int orig; - int sign; - int gain; - int i; - - /* Unlike the xing header, the lame tag has a fixed length. Fail if - * not all 36 bytes (288 bits) are there. */ - if (*bitlen < 288) - return false; - - for (i = 0; i < 9; i++) - lame->encoder[i] = (char)mad_bit_read(ptr, 8); - lame->encoder[9] = '\0'; - - *bitlen -= 72; - - /* This is technically incorrect, since the encoder might not be lame. - * But there's no other way to determine if this is a lame tag, and we - * wouldn't want to go reading a tag that's not there. */ - if (!g_str_has_prefix(lame->encoder, "LAME")) - return false; - - if (sscanf(lame->encoder+4, "%u.%u", - &lame->version.major, &lame->version.minor) != 2) - return false; - - g_debug("detected LAME version %i.%i (\"%s\")\n", - lame->version.major, lame->version.minor, lame->encoder); - - /* The reference volume was changed from the 83dB used in the - * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older - * versions, since everyone else uses 89dB instead of 83dB. - * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so - * it's impossible to make the proper adjustment for 3.95. - * Fortunately, 3.95 was only out for about a day before 3.95.1 was - * released. -- tmz */ - if (lame->version.major < 3 || - (lame->version.major == 3 && lame->version.minor < 95)) - adj = 6; - - mad_bit_read(ptr, 16); - - lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ - g_debug("LAME peak found: %f\n", lame->peak); - - lame->track_gain = 0; - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 1 && orig != 0) { - lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; - g_debug("LAME track gain found: %f\n", lame->track_gain); - } - - /* tmz reports that this isn't currently written by any version of lame - * (as of 3.97). Since we have no way of testing it, don't use it. - * Wouldn't want to go blowing someone's ears just because we read it - * wrong. :P -- jat */ - lame->album_gain = 0; -#if 0 - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 2 && orig != 0) { - lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj; - g_debug("LAME album gain found: %f\n", lame->track_gain); - } -#else - mad_bit_read(ptr, 16); -#endif - - mad_bit_read(ptr, 16); - - lame->encoder_delay = mad_bit_read(ptr, 12); - lame->encoder_padding = mad_bit_read(ptr, 12); - - g_debug("encoder delay is %i, encoder padding is %i\n", - lame->encoder_delay, lame->encoder_padding); - - mad_bit_read(ptr, 80); - - lame->crc = mad_bit_read(ptr, 16); - - *bitlen -= 216; - - return true; -} - -static inline float -mp3_frame_duration(const struct mad_frame *frame) -{ - return mad_timer_count(frame->header.duration, - MAD_UNITS_MILLISECONDS) / 1000.0; -} - -static goffset -mp3_this_frame_offset(const struct mp3_data *data) -{ - goffset offset = data->input_stream->offset; - - if (data->stream.this_frame != NULL) - offset -= data->stream.bufend - data->stream.this_frame; - else - offset -= data->stream.bufend - data->stream.buffer; - - return offset; -} - -static goffset -mp3_rest_including_this_frame(const struct mp3_data *data) -{ - return data->input_stream->size - mp3_this_frame_offset(data); -} - -/** - * Attempt to calulcate the length of the song from filesize - */ -static void -mp3_filesize_to_song_length(struct mp3_data *data) -{ - goffset rest = mp3_rest_including_this_frame(data); - - if (rest > 0) { - float frame_duration = mp3_frame_duration(&data->frame); - - data->total_time = (rest * 8.0) / (data->frame).header.bitrate; - data->max_frames = data->total_time / frame_duration + - FRAMES_CUSHION; - } else { - data->max_frames = FRAMES_CUSHION; - data->total_time = 0; - } -} - -static bool -mp3_decode_first_frame(struct mp3_data *data, struct tag **tag) -{ - struct xing xing; - struct lame lame; - struct mad_bitptr ptr; - int bitlen; - enum mp3_action ret; - - /* stfu gcc */ - memset(&xing, 0, sizeof(struct xing)); - xing.flags = 0; - - while (true) { - do { - ret = decode_next_frame_header(data, tag); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_SKIP) continue; - - do { - ret = decodeNextFrame(data); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_OK) break; - } - - ptr = data->stream.anc_ptr; - bitlen = data->stream.anc_bitlen; - - mp3_filesize_to_song_length(data); - - /* - * if an xing tag exists, use that! - */ - if (parse_xing(&xing, &ptr, &bitlen)) { - data->found_xing = true; - data->mute_frame = MUTEFRAME_SKIP; - - if ((xing.flags & XING_FRAMES) && xing.frames) { - mad_timer_t duration = data->frame.header.duration; - mad_timer_multiply(&duration, xing.frames); - data->total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; - data->max_frames = xing.frames; - } - - if (parse_lame(&lame, &ptr, &bitlen)) { - if (gapless_playback && - data->input_stream->seekable) { - data->drop_start_samples = lame.encoder_delay + - DECODERDELAY; - data->drop_end_samples = lame.encoder_padding; - } - - /* Album gain isn't currently used. See comment in - * parse_lame() for details. -- jat */ - if (data->decoder != NULL && - !data->found_replay_gain && - lame.track_gain) { - struct replay_gain_info *rgi - = replay_gain_info_new(); - rgi->tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; - rgi->tuples[REPLAY_GAIN_TRACK].peak = lame.peak; - decoder_replay_gain(data->decoder, rgi); - replay_gain_info_free(rgi); - } - } - } - - if (!data->max_frames) - return false; - - if (data->max_frames > 8 * 1024 * 1024) { - g_warning("mp3 file header indicates too many frames: %lu\n", - data->max_frames); - return false; - } - - data->frame_offsets = g_malloc(sizeof(long) * data->max_frames); - data->times = g_malloc(sizeof(mad_timer_t) * data->max_frames); - - return true; -} - -static void mp3_data_finish(struct mp3_data *data) -{ - mad_synth_finish(&data->synth); - mad_frame_finish(&data->frame); - mad_stream_finish(&data->stream); - - g_free(data->frame_offsets); - g_free(data->times); -} - -/* this is primarily used for getting total time for tags */ -static int -mad_decoder_total_file_time(struct input_stream *is) -{ - struct mp3_data data; - int ret; - - mp3_data_init(&data, NULL, is); - if (!mp3_decode_first_frame(&data, NULL)) - ret = -1; - else - ret = data.total_time + 0.5; - mp3_data_finish(&data); - - return ret; -} - -static bool -mp3_open(struct input_stream *is, struct mp3_data *data, - struct decoder *decoder, struct tag **tag) -{ - mp3_data_init(data, decoder, is); - *tag = NULL; - if (!mp3_decode_first_frame(data, tag)) { - mp3_data_finish(data); - if (tag && *tag) - tag_free(*tag); - return false; - } - - return true; -} - -static long -mp3_time_to_frame(const struct mp3_data *data, double t) -{ - unsigned long i; - - for (i = 0; i < data->highest_frame; ++i) { - double frame_time = - mad_timer_count(data->times[i], - MAD_UNITS_MILLISECONDS) / 1000.; - if (frame_time >= t) - break; - } - - return i; -} - -static void -mp3_update_timer_next_frame(struct mp3_data *data) -{ - if (data->current_frame >= data->highest_frame) { - /* record this frame's properties in - data->frame_offsets (for seeking) and - data->times */ - data->bit_rate = (data->frame).header.bitrate; - - if (data->current_frame >= data->max_frames) - /* cap data->current_frame */ - data->current_frame = data->max_frames - 1; - else - data->highest_frame++; - - data->frame_offsets[data->current_frame] = - mp3_this_frame_offset(data); - - mad_timer_add(&data->timer, (data->frame).header.duration); - data->times[data->current_frame] = data->timer; - } else - /* get the new timer value from data->times */ - data->timer = data->times[data->current_frame]; - - data->current_frame++; - data->elapsed_time = - mad_timer_count(data->timer, MAD_UNITS_MILLISECONDS) / 1000.0; -} - -/** - * Sends the synthesized current frame via decoder_data(). - */ -static enum decoder_command -mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length) -{ - unsigned max_samples; - - max_samples = sizeof(data->output_buffer) / - sizeof(data->output_buffer[0]) / - MAD_NCHANNELS(&(data->frame).header); - - while (i < pcm_length) { - enum decoder_command cmd; - unsigned int num_samples = pcm_length - i; - if (num_samples > max_samples) - num_samples = max_samples; - - i += num_samples; - - mad_fixed_to_24_buffer(data->output_buffer, - &data->synth, - i - num_samples, i, - MAD_NCHANNELS(&(data->frame).header)); - num_samples *= MAD_NCHANNELS(&(data->frame).header); - - cmd = decoder_data(data->decoder, data->input_stream, - data->output_buffer, - sizeof(data->output_buffer[0]) * num_samples, - data->bit_rate / 1000); - if (cmd != DECODE_COMMAND_NONE) - return cmd; - } - - return DECODE_COMMAND_NONE; -} - -/** - * Synthesize the current frame and send it via decoder_data(). - */ -static enum decoder_command -mp3_synth_and_send(struct mp3_data *data) -{ - unsigned i, pcm_length; - enum decoder_command cmd; - - mad_synth_frame(&data->synth, &data->frame); - - if (!data->found_first_frame) { - unsigned int samples_per_frame = data->synth.pcm.length; - data->drop_start_frames = data->drop_start_samples / samples_per_frame; - data->drop_end_frames = data->drop_end_samples / samples_per_frame; - data->drop_start_samples = data->drop_start_samples % samples_per_frame; - data->drop_end_samples = data->drop_end_samples % samples_per_frame; - data->found_first_frame = true; - } - - if (data->drop_start_frames > 0) { - data->drop_start_frames--; - return DECODE_COMMAND_NONE; - } else if ((data->drop_end_frames > 0) && - (data->current_frame == (data->max_frames + 1 - data->drop_end_frames))) { - /* stop decoding, effectively dropping all remaining - frames */ - return DECODE_COMMAND_STOP; - } - - if (!data->decoded_first_frame) { - i = data->drop_start_samples; - data->decoded_first_frame = true; - } else - i = 0; - - pcm_length = data->synth.pcm.length; - if (data->drop_end_samples && - (data->current_frame == data->max_frames - data->drop_end_frames)) { - if (data->drop_end_samples >= pcm_length) - pcm_length = 0; - else - pcm_length -= data->drop_end_samples; - } - - cmd = mp3_send_pcm(data, i, pcm_length); - if (cmd != DECODE_COMMAND_NONE) - return cmd; - - if (data->drop_end_samples && - (data->current_frame == data->max_frames - data->drop_end_frames)) - /* stop decoding, effectively dropping - * all remaining samples */ - return DECODE_COMMAND_STOP; - - return DECODE_COMMAND_NONE; -} - -static bool -mp3_read(struct mp3_data *data) -{ - struct decoder *decoder = data->decoder; - enum mp3_action ret; - enum decoder_command cmd; - - mp3_update_timer_next_frame(data); - - switch (data->mute_frame) { - case MUTEFRAME_SKIP: - data->mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_SEEK: - if (data->elapsed_time >= data->seek_where) - data->mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_NONE: - cmd = mp3_synth_and_send(data); - if (cmd == DECODE_COMMAND_SEEK) { - unsigned long j; - - assert(data->input_stream->seekable); - - j = mp3_time_to_frame(data, - decoder_seek_where(decoder)); - if (j < data->highest_frame) { - if (mp3_seek(data, data->frame_offsets[j])) { - data->current_frame = j; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else { - data->seek_where = decoder_seek_where(decoder); - data->mute_frame = MUTEFRAME_SEEK; - decoder_command_finished(decoder); - } - } else if (cmd != DECODE_COMMAND_NONE) - return false; - } - - while (true) { - bool skip = false; - - do { - struct tag *tag = NULL; - - ret = decode_next_frame_header(data, &tag); - - if (tag != NULL) { - decoder_tag(decoder, data->input_stream, tag); - tag_free(tag); - } - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - else if (ret == DECODE_SKIP) - skip = true; - - if (data->mute_frame == MUTEFRAME_NONE) { - do { - ret = decodeNextFrame(data); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - } - - if (!skip && ret == DECODE_OK) - break; - } - - return ret != DECODE_BREAK; -} - -static void -mp3_decode(struct decoder *decoder, struct input_stream *input_stream) -{ - struct mp3_data data; - GError *error = NULL; - struct tag *tag = NULL; - struct audio_format audio_format; - - if (!mp3_open(input_stream, &data, decoder, &tag)) { - if (decoder_get_command(decoder) == DECODE_COMMAND_NONE) - g_warning - ("Input does not appear to be a mp3 bit stream.\n"); - return; - } - - if (!audio_format_init_checked(&audio_format, - data.frame.header.samplerate, - SAMPLE_FORMAT_S24_P32, - MAD_NCHANNELS(&data.frame.header), - &error)) { - g_warning("%s", error->message); - g_error_free(error); - - if (tag != NULL) - tag_free(tag); - mp3_data_finish(&data); - return; - } - - decoder_initialized(decoder, &audio_format, - data.input_stream->seekable, data.total_time); - - if (tag != NULL) { - decoder_tag(decoder, input_stream, tag); - tag_free(tag); - } - - while (mp3_read(&data)) ; - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && - data.mute_frame == MUTEFRAME_SEEK) - decoder_command_finished(decoder); - - mp3_data_finish(&data); -} - -static struct tag * -mad_decoder_stream_tag(struct input_stream *is) -{ - struct tag *tag; - int total_time; - - total_time = mad_decoder_total_file_time(is); - if (total_time < 0) - return NULL; - - tag = tag_new(); - tag->time = total_time; - return tag; -} - -static const char *const mp3_suffixes[] = { "mp3", "mp2", NULL }; -static const char *const mp3_mime_types[] = { "audio/mpeg", NULL }; - -const struct decoder_plugin mad_decoder_plugin = { - .name = "mad", - .init = mp3_plugin_init, - .stream_decode = mp3_decode, - .stream_tag = mad_decoder_stream_tag, - .suffixes = mp3_suffixes, - .mime_types = mp3_mime_types -}; diff --git a/src/decoder/mikmod_decoder_plugin.c b/src/decoder/mikmod_decoder_plugin.c new file mode 100644 index 000000000..69212da59 --- /dev/null +++ b/src/decoder/mikmod_decoder_plugin.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mikmod" + +/* this is largely copied from alsaplayer */ + +#define MIKMOD_FRAME_SIZE 4096 + +static BOOL +mikmod_mpd_init(void) +{ + return VC_Init(); +} + +static void +mikmod_mpd_exit(void) +{ + VC_Exit(); +} + +static void +mikmod_mpd_update(void) +{ +} + +static BOOL +mikmod_mpd_is_present(void) +{ + return true; +} + +static char drv_name[] = PACKAGE_NAME; +static char drv_version[] = VERSION; + +#if (LIBMIKMOD_VERSION > 0x030106) +static char drv_alias[] = PACKAGE; +#endif + +static MDRIVER drv_mpd = { + NULL, + drv_name, + drv_version, + 0, + 255, +#if (LIBMIKMOD_VERSION > 0x030106) + drv_alias, +#if (LIBMIKMOD_VERSION >= 0x030200) + NULL, /* CmdLineHelp */ +#endif + NULL, /* CommandLine */ +#endif + mikmod_mpd_is_present, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + mikmod_mpd_init, + mikmod_mpd_exit, + NULL, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + mikmod_mpd_update, + NULL, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + +static unsigned mikmod_sample_rate; + +static bool +mikmod_decoder_init(const struct config_param *param) +{ + static char params[] = ""; + + mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate", + 44100); + if (!audio_valid_sample_rate(mikmod_sample_rate)) + g_error("Invalid sample rate in line %d: %u", + param->line, mikmod_sample_rate); + + md_device = 0; + md_reverb = 0; + + MikMod_RegisterDriver(&drv_mpd); + MikMod_RegisterAllLoaders(); + + md_pansep = 64; + md_mixfreq = mikmod_sample_rate; + md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | + DMODE_16BITS); + + if (MikMod_Init(params)) { + g_warning("Could not init MikMod: %s\n", + MikMod_strerror(MikMod_errno)); + return false; + } + + return true; +} + +static void +mikmod_decoder_finish(void) +{ + MikMod_Exit(); +} + +static void +mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs) +{ + char *path2; + MODULE *handle; + struct audio_format audio_format; + int ret; + SBYTE buffer[MIKMOD_FRAME_SIZE]; + enum decoder_command cmd = DECODE_COMMAND_NONE; + + path2 = g_strdup(path_fs); + handle = Player_Load(path2, 128, 0); + g_free(path2); + + if (handle == NULL) { + g_warning("failed to open mod: %s", path_fs); + return; + } + + /* Prevent module from looping forever */ + handle->loop = 0; + + audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, false, 0); + + Player_Start(handle); + while (cmd == DECODE_COMMAND_NONE && Player_Active()) { + ret = VC_WriteBytes(buffer, sizeof(buffer)); + cmd = decoder_data(decoder, NULL, buffer, ret, 0); + } + + Player_Stop(); + Player_Free(handle); +} + +static struct tag * +mikmod_decoder_tag_dup(const char *path_fs) +{ + char *path2; + struct tag *ret = NULL; + MODULE *handle; + char *title; + + path2 = g_strdup(path_fs); + handle = Player_Load(path2, 128, 0); + g_free(path2); + + if (handle == NULL) { + g_debug("Failed to open file: %s", path_fs); + return NULL; + + } + Player_Free(handle); + + ret = tag_new(); + + ret->time = 0; + + path2 = g_strdup(path_fs); + title = g_strdup(Player_LoadTitle(path2)); + g_free(path2); + if (title) + tag_add_item(ret, TAG_TITLE, title); + + return ret; +} + +static const char *const mikmod_decoder_suffixes[] = { + "amf", + "dsm", + "far", + "gdm", + "imf", + "it", + "med", + "mod", + "mtm", + "s3m", + "stm", + "stx", + "ult", + "uni", + "xm", + NULL +}; + +const struct decoder_plugin mikmod_decoder_plugin = { + .name = "mikmod", + .init = mikmod_decoder_init, + .finish = mikmod_decoder_finish, + .file_decode = mikmod_decoder_file_decode, + .tag_dup = mikmod_decoder_tag_dup, + .suffixes = mikmod_decoder_suffixes, +}; diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_plugin.c deleted file mode 100644 index 69212da59..000000000 --- a/src/decoder/mikmod_plugin.c +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mikmod" - -/* this is largely copied from alsaplayer */ - -#define MIKMOD_FRAME_SIZE 4096 - -static BOOL -mikmod_mpd_init(void) -{ - return VC_Init(); -} - -static void -mikmod_mpd_exit(void) -{ - VC_Exit(); -} - -static void -mikmod_mpd_update(void) -{ -} - -static BOOL -mikmod_mpd_is_present(void) -{ - return true; -} - -static char drv_name[] = PACKAGE_NAME; -static char drv_version[] = VERSION; - -#if (LIBMIKMOD_VERSION > 0x030106) -static char drv_alias[] = PACKAGE; -#endif - -static MDRIVER drv_mpd = { - NULL, - drv_name, - drv_version, - 0, - 255, -#if (LIBMIKMOD_VERSION > 0x030106) - drv_alias, -#if (LIBMIKMOD_VERSION >= 0x030200) - NULL, /* CmdLineHelp */ -#endif - NULL, /* CommandLine */ -#endif - mikmod_mpd_is_present, - VC_SampleLoad, - VC_SampleUnload, - VC_SampleSpace, - VC_SampleLength, - mikmod_mpd_init, - mikmod_mpd_exit, - NULL, - VC_SetNumVoices, - VC_PlayStart, - VC_PlayStop, - mikmod_mpd_update, - NULL, - VC_VoiceSetVolume, - VC_VoiceGetVolume, - VC_VoiceSetFrequency, - VC_VoiceGetFrequency, - VC_VoiceSetPanning, - VC_VoiceGetPanning, - VC_VoicePlay, - VC_VoiceStop, - VC_VoiceStopped, - VC_VoiceGetPosition, - VC_VoiceRealVolume -}; - -static unsigned mikmod_sample_rate; - -static bool -mikmod_decoder_init(const struct config_param *param) -{ - static char params[] = ""; - - mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate", - 44100); - if (!audio_valid_sample_rate(mikmod_sample_rate)) - g_error("Invalid sample rate in line %d: %u", - param->line, mikmod_sample_rate); - - md_device = 0; - md_reverb = 0; - - MikMod_RegisterDriver(&drv_mpd); - MikMod_RegisterAllLoaders(); - - md_pansep = 64; - md_mixfreq = mikmod_sample_rate; - md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | - DMODE_16BITS); - - if (MikMod_Init(params)) { - g_warning("Could not init MikMod: %s\n", - MikMod_strerror(MikMod_errno)); - return false; - } - - return true; -} - -static void -mikmod_decoder_finish(void) -{ - MikMod_Exit(); -} - -static void -mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs) -{ - char *path2; - MODULE *handle; - struct audio_format audio_format; - int ret; - SBYTE buffer[MIKMOD_FRAME_SIZE]; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - path2 = g_strdup(path_fs); - handle = Player_Load(path2, 128, 0); - g_free(path2); - - if (handle == NULL) { - g_warning("failed to open mod: %s", path_fs); - return; - } - - /* Prevent module from looping forever */ - handle->loop = 0; - - audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2); - assert(audio_format_valid(&audio_format)); - - decoder_initialized(decoder, &audio_format, false, 0); - - Player_Start(handle); - while (cmd == DECODE_COMMAND_NONE && Player_Active()) { - ret = VC_WriteBytes(buffer, sizeof(buffer)); - cmd = decoder_data(decoder, NULL, buffer, ret, 0); - } - - Player_Stop(); - Player_Free(handle); -} - -static struct tag * -mikmod_decoder_tag_dup(const char *path_fs) -{ - char *path2; - struct tag *ret = NULL; - MODULE *handle; - char *title; - - path2 = g_strdup(path_fs); - handle = Player_Load(path2, 128, 0); - g_free(path2); - - if (handle == NULL) { - g_debug("Failed to open file: %s", path_fs); - return NULL; - - } - Player_Free(handle); - - ret = tag_new(); - - ret->time = 0; - - path2 = g_strdup(path_fs); - title = g_strdup(Player_LoadTitle(path2)); - g_free(path2); - if (title) - tag_add_item(ret, TAG_TITLE, title); - - return ret; -} - -static const char *const mikmod_decoder_suffixes[] = { - "amf", - "dsm", - "far", - "gdm", - "imf", - "it", - "med", - "mod", - "mtm", - "s3m", - "stm", - "stx", - "ult", - "uni", - "xm", - NULL -}; - -const struct decoder_plugin mikmod_decoder_plugin = { - .name = "mikmod", - .init = mikmod_decoder_init, - .finish = mikmod_decoder_finish, - .file_decode = mikmod_decoder_file_decode, - .tag_dup = mikmod_decoder_tag_dup, - .suffixes = mikmod_decoder_suffixes, -}; diff --git a/src/decoder/modplug_decoder_plugin.c b/src/decoder/modplug_decoder_plugin.c new file mode 100644 index 000000000..037c2fd74 --- /dev/null +++ b/src/decoder/modplug_decoder_plugin.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "modplug" + +enum { + MODPLUG_FRAME_SIZE = 4096, + MODPLUG_PREALLOC_BLOCK = 256 * 1024, + MODPLUG_READ_BLOCK = 128 * 1024, + MODPLUG_FILE_LIMIT = 100 * 1024 * 1024, +}; + +static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is) +{ + unsigned char *data; + GByteArray *bdatas; + size_t ret; + + if (is->size == 0) { + g_warning("file is empty"); + return NULL; + } + + if (is->size > MODPLUG_FILE_LIMIT) { + g_warning("file too large"); + return NULL; + } + + //known/unknown size, preallocate array, lets read in chunks + if (is->size > 0) { + bdatas = g_byte_array_sized_new(is->size); + } else { + bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK); + } + + data = g_malloc(MODPLUG_READ_BLOCK); + + while (true) { + ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK); + if (ret == 0) { + if (input_stream_eof(is)) + /* end of file */ + break; + + /* I/O error - skip this song */ + g_free(data); + g_byte_array_free(bdatas, true); + return NULL; + } + + if (bdatas->len + ret > MODPLUG_FILE_LIMIT) { + g_warning("stream too large\n"); + g_free(data); + g_byte_array_free(bdatas, TRUE); + return NULL; + } + + g_byte_array_append(bdatas, data, ret); + } + + g_free(data); + + return bdatas; +} + +static void +mod_decode(struct decoder *decoder, struct input_stream *is) +{ + ModPlugFile *f; + ModPlug_Settings settings; + GByteArray *bdatas; + struct audio_format audio_format; + int ret; + char audio_buffer[MODPLUG_FRAME_SIZE]; + enum decoder_command cmd = DECODE_COMMAND_NONE; + + bdatas = mod_loadfile(decoder, is); + + if (!bdatas) { + g_warning("could not load stream\n"); + return; + } + + ModPlug_GetSettings(&settings); + /* alter setting */ + settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ + settings.mChannels = 2; + settings.mBits = 16; + settings.mFrequency = 44100; + /* insert more setting changes here */ + ModPlug_SetSettings(&settings); + + f = ModPlug_Load(bdatas->data, bdatas->len); + g_byte_array_free(bdatas, TRUE); + if (!f) { + g_warning("could not decode stream\n"); + return; + } + + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, + is->seekable, ModPlug_GetLength(f) / 1000.0); + + do { + ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); + if (ret <= 0) + break; + + cmd = decoder_data(decoder, NULL, + audio_buffer, ret, + 0); + + if (cmd == DECODE_COMMAND_SEEK) { + float where = decoder_seek_where(decoder); + + ModPlug_Seek(f, (int)(where * 1000.0)); + + decoder_command_finished(decoder); + } + + } while (cmd != DECODE_COMMAND_STOP); + + ModPlug_Unload(f); +} + +static struct tag * +modplug_stream_tag(struct input_stream *is) +{ + ModPlugFile *f; + struct tag *ret = NULL; + GByteArray *bdatas; + char *title; + + bdatas = mod_loadfile(NULL, is); + if (!bdatas) + return NULL; + + f = ModPlug_Load(bdatas->data, bdatas->len); + g_byte_array_free(bdatas, TRUE); + if (f == NULL) + return NULL; + + ret = tag_new(); + ret->time = ModPlug_GetLength(f) / 1000; + + title = g_strdup(ModPlug_GetName(f)); + if (title) + tag_add_item(ret, TAG_TITLE, title); + g_free(title); + + ModPlug_Unload(f); + + return ret; +} + +static const char *const mod_suffixes[] = { + "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it", + "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm", + "ult", "umx", "xm", + NULL +}; + +const struct decoder_plugin modplug_decoder_plugin = { + .name = "modplug", + .stream_decode = mod_decode, + .stream_tag = modplug_stream_tag, + .suffixes = mod_suffixes, +}; diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c deleted file mode 100644 index 037c2fd74..000000000 --- a/src/decoder/modplug_plugin.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "modplug" - -enum { - MODPLUG_FRAME_SIZE = 4096, - MODPLUG_PREALLOC_BLOCK = 256 * 1024, - MODPLUG_READ_BLOCK = 128 * 1024, - MODPLUG_FILE_LIMIT = 100 * 1024 * 1024, -}; - -static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is) -{ - unsigned char *data; - GByteArray *bdatas; - size_t ret; - - if (is->size == 0) { - g_warning("file is empty"); - return NULL; - } - - if (is->size > MODPLUG_FILE_LIMIT) { - g_warning("file too large"); - return NULL; - } - - //known/unknown size, preallocate array, lets read in chunks - if (is->size > 0) { - bdatas = g_byte_array_sized_new(is->size); - } else { - bdatas = g_byte_array_sized_new(MODPLUG_PREALLOC_BLOCK); - } - - data = g_malloc(MODPLUG_READ_BLOCK); - - while (true) { - ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK); - if (ret == 0) { - if (input_stream_eof(is)) - /* end of file */ - break; - - /* I/O error - skip this song */ - g_free(data); - g_byte_array_free(bdatas, true); - return NULL; - } - - if (bdatas->len + ret > MODPLUG_FILE_LIMIT) { - g_warning("stream too large\n"); - g_free(data); - g_byte_array_free(bdatas, TRUE); - return NULL; - } - - g_byte_array_append(bdatas, data, ret); - } - - g_free(data); - - return bdatas; -} - -static void -mod_decode(struct decoder *decoder, struct input_stream *is) -{ - ModPlugFile *f; - ModPlug_Settings settings; - GByteArray *bdatas; - struct audio_format audio_format; - int ret; - char audio_buffer[MODPLUG_FRAME_SIZE]; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - bdatas = mod_loadfile(decoder, is); - - if (!bdatas) { - g_warning("could not load stream\n"); - return; - } - - ModPlug_GetSettings(&settings); - /* alter setting */ - settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ - settings.mChannels = 2; - settings.mBits = 16; - settings.mFrequency = 44100; - /* insert more setting changes here */ - ModPlug_SetSettings(&settings); - - f = ModPlug_Load(bdatas->data, bdatas->len); - g_byte_array_free(bdatas, TRUE); - if (!f) { - g_warning("could not decode stream\n"); - return; - } - - audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); - assert(audio_format_valid(&audio_format)); - - decoder_initialized(decoder, &audio_format, - is->seekable, ModPlug_GetLength(f) / 1000.0); - - do { - ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, NULL, - audio_buffer, ret, - 0); - - if (cmd == DECODE_COMMAND_SEEK) { - float where = decoder_seek_where(decoder); - - ModPlug_Seek(f, (int)(where * 1000.0)); - - decoder_command_finished(decoder); - } - - } while (cmd != DECODE_COMMAND_STOP); - - ModPlug_Unload(f); -} - -static struct tag * -modplug_stream_tag(struct input_stream *is) -{ - ModPlugFile *f; - struct tag *ret = NULL; - GByteArray *bdatas; - char *title; - - bdatas = mod_loadfile(NULL, is); - if (!bdatas) - return NULL; - - f = ModPlug_Load(bdatas->data, bdatas->len); - g_byte_array_free(bdatas, TRUE); - if (f == NULL) - return NULL; - - ret = tag_new(); - ret->time = ModPlug_GetLength(f) / 1000; - - title = g_strdup(ModPlug_GetName(f)); - if (title) - tag_add_item(ret, TAG_TITLE, title); - g_free(title); - - ModPlug_Unload(f); - - return ret; -} - -static const char *const mod_suffixes[] = { - "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it", - "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm", - "ult", "umx", "xm", - NULL -}; - -const struct decoder_plugin modplug_decoder_plugin = { - .name = "modplug", - .stream_decode = mod_decode, - .stream_tag = modplug_stream_tag, - .suffixes = mod_suffixes, -}; diff --git a/src/decoder/mp4ff_decoder_plugin.c b/src/decoder/mp4ff_decoder_plugin.c new file mode 100644 index 000000000..74fa13339 --- /dev/null +++ b/src/decoder/mp4ff_decoder_plugin.c @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" + +#include + +#include +#include + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mp4ff" + +/* all code here is either based on or copied from FAAD2's frontend code */ + +struct mp4_context { + struct decoder *decoder; + struct input_stream *input_stream; +}; + +static int +mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder, + uint32_t *sample_rate, unsigned char *channels_r) +{ +#ifdef HAVE_FAAD_LONG + /* neaacdec.h declares all arguments as "unsigned long", but + internally expects uint32_t pointers. To avoid gcc + warnings, use this workaround. */ + unsigned long *sample_rate_r = (unsigned long*)sample_rate; +#else + uint32_t *sample_rate_r = sample_rate; +#endif + int i, rc; + int num_tracks = mp4ff_total_tracks(infile); + + for (i = 0; i < num_tracks; i++) { + unsigned char *buff = NULL; + unsigned int buff_size = 0; + + if (mp4ff_get_track_type(infile, i) != 1) + /* not an audio track */ + continue; + + if (decoder == NULL) + /* have don't have a decoder to initialize - + we're done now, because we found an audio + track */ + return i; + + mp4ff_get_decoder_config(infile, i, &buff, &buff_size); + if (buff == NULL) + continue; + + rc = faacDecInit2(decoder, buff, buff_size, + sample_rate_r, channels_r); + free(buff); + + if (rc >= 0) + /* found a valid AAC track */ + return i; + } + + /* can't decode this */ + return -1; +} + +static uint32_t +mp4_read(void *user_data, void *buffer, uint32_t length) +{ + struct mp4_context *ctx = user_data; + + return decoder_read(ctx->decoder, ctx->input_stream, buffer, length); +} + +static uint32_t +mp4_seek(void *user_data, uint64_t position) +{ + struct mp4_context *ctx = user_data; + + return input_stream_seek(ctx->input_stream, position, SEEK_SET, NULL) + ? 0 : -1; +} + +static faacDecHandle +mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) +{ + faacDecHandle decoder; + faacDecConfigurationPtr config; + int track; + uint32_t sample_rate; + unsigned char channels; + GError *error = NULL; + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; +#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX + config->downMatrix = 1; +#endif +#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR + config->dontUpSampleImplicitSBR = 0; +#endif + faacDecSetConfiguration(decoder, config); + + track = mp4_get_aac_track(mp4fh, decoder, &sample_rate, &channels); + if (track < 0) { + g_warning("No AAC track found"); + faacDecClose(decoder); + return NULL; + } + + if (!audio_format_init_checked(audio_format, sample_rate, + SAMPLE_FORMAT_S16, channels, + &error)) { + g_warning("%s", error->message); + g_error_free(error); + faacDecClose(decoder); + return NULL; + } + + *track_r = track; + + return decoder; +} + +static void +mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) +{ + struct mp4_context ctx = { + .decoder = mpd_decoder, + .input_stream = input_stream, + }; + mp4ff_callback_t callback = { + .read = mp4_read, + .seek = mp4_seek, + .user_data = &ctx, + }; + mp4ff_t *mp4fh; + int32_t track; + float file_time, total_time; + int32_t scale; + faacDecHandle decoder; + struct audio_format audio_format; + faacDecFrameInfo frame_info; + unsigned char *mp4_buffer; + unsigned int mp4_buffer_size; + long sample_id; + long num_samples; + long dur; + unsigned int sample_count; + char *sample_buffer; + size_t sample_buffer_length; + unsigned int initial = 1; + float *seek_table; + long seek_table_end = -1; + bool seek_position_found = false; + long offset; + uint16_t bit_rate = 0; + bool seeking = false; + double seek_where = 0; + enum decoder_command cmd = DECODE_COMMAND_NONE; + + mp4fh = mp4ff_open_read(&callback); + if (!mp4fh) { + g_warning("Input does not appear to be a mp4 stream.\n"); + return; + } + + decoder = mp4_faad_new(mp4fh, &track, &audio_format); + if (decoder == NULL) { + mp4ff_close(mp4fh); + return; + } + + file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); + scale = mp4ff_time_scale(mp4fh, track); + + if (scale < 0) { + g_warning("Error getting audio format of mp4 AAC track.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + return; + } + total_time = ((float)file_time) / scale; + + num_samples = mp4ff_num_samples(mp4fh, track); + if (num_samples > (long)(G_MAXINT / sizeof(float))) { + g_warning("Integer overflow.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + return; + } + + file_time = 0.0; + + seek_table = input_stream->seekable + ? g_malloc(sizeof(float) * num_samples) + : NULL; + + decoder_initialized(mpd_decoder, &audio_format, + input_stream->seekable, + total_time); + + for (sample_id = 0; + sample_id < num_samples && cmd != DECODE_COMMAND_STOP; + sample_id++) { + if (cmd == DECODE_COMMAND_SEEK) { + assert(seek_table != NULL); + + seeking = true; + seek_where = decoder_seek_where(mpd_decoder); + } + + if (seeking && seek_table_end > 1 && + seek_table[seek_table_end] >= seek_where) { + int i = 2; + + assert(seek_table != NULL); + + while (seek_table[i] < seek_where) + i++; + sample_id = i - 1; + file_time = seek_table[sample_id]; + } + + dur = mp4ff_get_sample_duration(mp4fh, track, sample_id); + offset = mp4ff_get_sample_offset(mp4fh, track, sample_id); + + if (seek_table != NULL && sample_id > seek_table_end) { + seek_table[sample_id] = file_time; + seek_table_end = sample_id; + } + + if (sample_id == 0) + dur = 0; + if (offset > dur) + dur = 0; + else + dur -= offset; + file_time += ((float)dur) / scale; + + if (seeking && file_time >= seek_where) + seek_position_found = true; + + if (seeking && seek_position_found) { + seek_position_found = false; + seeking = 0; + decoder_command_finished(mpd_decoder); + } + + if (seeking) + continue; + + if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer, + &mp4_buffer_size) == 0) + break; + +#ifdef HAVE_FAAD_BUFLEN_FUNCS + sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer, + mp4_buffer_size); +#else + sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer); +#endif + + free(mp4_buffer); + + if (frame_info.error > 0) { + g_warning("faad2 error: %s\n", + faacDecGetErrorMessage(frame_info.error)); + break; + } + + if (frame_info.channels != audio_format.channels) { + g_warning("channel count changed from %u to %u", + audio_format.channels, frame_info.channels); + break; + } + +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + if (frame_info.samplerate != audio_format.sample_rate) { + g_warning("sample rate changed from %u to %lu", + audio_format.sample_rate, + (unsigned long)frame_info.samplerate); + break; + } +#endif + + if (audio_format.channels * (unsigned long)(dur + offset) > frame_info.samples) { + dur = frame_info.samples / audio_format.channels; + offset = 0; + } + + sample_count = (unsigned long)(dur * audio_format.channels); + + if (sample_count > 0) { + initial = 0; + bit_rate = frame_info.bytesconsumed * 8.0 * + frame_info.channels * scale / + frame_info.samples / 1000 + 0.5; + } + + sample_buffer_length = sample_count * 2; + + sample_buffer += offset * audio_format.channels * 2; + + cmd = decoder_data(mpd_decoder, input_stream, + sample_buffer, sample_buffer_length, + bit_rate); + } + + g_free(seek_table); + faacDecClose(decoder); + mp4ff_close(mp4fh); +} + +static struct tag * +mp4_stream_tag(struct input_stream *is) +{ + struct tag *ret = NULL; + struct mp4_context ctx = { + .decoder = NULL, + .input_stream = is, + }; + mp4ff_callback_t callback = { + .read = mp4_read, + .seek = mp4_seek, + .user_data = &ctx, + }; + mp4ff_t *mp4fh; + int32_t track; + int32_t file_time; + int32_t scale; + int i; + + mp4fh = mp4ff_open_read(&callback); + if (mp4fh == NULL) + return NULL; + + track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); + if (track < 0) { + mp4ff_close(mp4fh); + return NULL; + } + + ret = tag_new(); + file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); + scale = mp4ff_time_scale(mp4fh, track); + if (scale < 0) { + mp4ff_close(mp4fh); + tag_free(ret); + return NULL; + } + ret->time = ((float)file_time) / scale + 0.5; + + for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { + char *item; + char *value; + + mp4ff_meta_get_by_index(mp4fh, i, &item, &value); + + if (0 == g_ascii_strcasecmp("artist", item)) { + tag_add_item(ret, TAG_ARTIST, value); + } else if (0 == g_ascii_strcasecmp("title", item)) { + tag_add_item(ret, TAG_TITLE, value); + } else if (0 == g_ascii_strcasecmp("album", item)) { + tag_add_item(ret, TAG_ALBUM, value); + } else if (0 == g_ascii_strcasecmp("track", item)) { + tag_add_item(ret, TAG_TRACK, value); + } else if (0 == g_ascii_strcasecmp("disc", item)) { + /* Is that the correct id? */ + tag_add_item(ret, TAG_DISC, value); + } else if (0 == g_ascii_strcasecmp("genre", item)) { + tag_add_item(ret, TAG_GENRE, value); + } else if (0 == g_ascii_strcasecmp("date", item)) { + tag_add_item(ret, TAG_DATE, value); + } else if (0 == g_ascii_strcasecmp("writer", item)) { + tag_add_item(ret, TAG_COMPOSER, value); + } + + free(item); + free(value); + } + + mp4ff_close(mp4fh); + + return ret; +} + +static const char *const mp4_suffixes[] = { "m4a", "mp4", NULL }; +static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; + +const struct decoder_plugin mp4ff_decoder_plugin = { + .name = "mp4", + .stream_decode = mp4_decode, + .stream_tag = mp4_stream_tag, + .suffixes = mp4_suffixes, + .mime_types = mp4_mime_types, +}; diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c deleted file mode 100644 index 74fa13339..000000000 --- a/src/decoder/mp4ff_plugin.c +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" - -#include - -#include -#include - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mp4ff" - -/* all code here is either based on or copied from FAAD2's frontend code */ - -struct mp4_context { - struct decoder *decoder; - struct input_stream *input_stream; -}; - -static int -mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder, - uint32_t *sample_rate, unsigned char *channels_r) -{ -#ifdef HAVE_FAAD_LONG - /* neaacdec.h declares all arguments as "unsigned long", but - internally expects uint32_t pointers. To avoid gcc - warnings, use this workaround. */ - unsigned long *sample_rate_r = (unsigned long*)sample_rate; -#else - uint32_t *sample_rate_r = sample_rate; -#endif - int i, rc; - int num_tracks = mp4ff_total_tracks(infile); - - for (i = 0; i < num_tracks; i++) { - unsigned char *buff = NULL; - unsigned int buff_size = 0; - - if (mp4ff_get_track_type(infile, i) != 1) - /* not an audio track */ - continue; - - if (decoder == NULL) - /* have don't have a decoder to initialize - - we're done now, because we found an audio - track */ - return i; - - mp4ff_get_decoder_config(infile, i, &buff, &buff_size); - if (buff == NULL) - continue; - - rc = faacDecInit2(decoder, buff, buff_size, - sample_rate_r, channels_r); - free(buff); - - if (rc >= 0) - /* found a valid AAC track */ - return i; - } - - /* can't decode this */ - return -1; -} - -static uint32_t -mp4_read(void *user_data, void *buffer, uint32_t length) -{ - struct mp4_context *ctx = user_data; - - return decoder_read(ctx->decoder, ctx->input_stream, buffer, length); -} - -static uint32_t -mp4_seek(void *user_data, uint64_t position) -{ - struct mp4_context *ctx = user_data; - - return input_stream_seek(ctx->input_stream, position, SEEK_SET, NULL) - ? 0 : -1; -} - -static faacDecHandle -mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) -{ - faacDecHandle decoder; - faacDecConfigurationPtr config; - int track; - uint32_t sample_rate; - unsigned char channels; - GError *error = NULL; - - decoder = faacDecOpen(); - - config = faacDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; -#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX - config->downMatrix = 1; -#endif -#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR - config->dontUpSampleImplicitSBR = 0; -#endif - faacDecSetConfiguration(decoder, config); - - track = mp4_get_aac_track(mp4fh, decoder, &sample_rate, &channels); - if (track < 0) { - g_warning("No AAC track found"); - faacDecClose(decoder); - return NULL; - } - - if (!audio_format_init_checked(audio_format, sample_rate, - SAMPLE_FORMAT_S16, channels, - &error)) { - g_warning("%s", error->message); - g_error_free(error); - faacDecClose(decoder); - return NULL; - } - - *track_r = track; - - return decoder; -} - -static void -mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) -{ - struct mp4_context ctx = { - .decoder = mpd_decoder, - .input_stream = input_stream, - }; - mp4ff_callback_t callback = { - .read = mp4_read, - .seek = mp4_seek, - .user_data = &ctx, - }; - mp4ff_t *mp4fh; - int32_t track; - float file_time, total_time; - int32_t scale; - faacDecHandle decoder; - struct audio_format audio_format; - faacDecFrameInfo frame_info; - unsigned char *mp4_buffer; - unsigned int mp4_buffer_size; - long sample_id; - long num_samples; - long dur; - unsigned int sample_count; - char *sample_buffer; - size_t sample_buffer_length; - unsigned int initial = 1; - float *seek_table; - long seek_table_end = -1; - bool seek_position_found = false; - long offset; - uint16_t bit_rate = 0; - bool seeking = false; - double seek_where = 0; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - mp4fh = mp4ff_open_read(&callback); - if (!mp4fh) { - g_warning("Input does not appear to be a mp4 stream.\n"); - return; - } - - decoder = mp4_faad_new(mp4fh, &track, &audio_format); - if (decoder == NULL) { - mp4ff_close(mp4fh); - return; - } - - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - - if (scale < 0) { - g_warning("Error getting audio format of mp4 AAC track.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - total_time = ((float)file_time) / scale; - - num_samples = mp4ff_num_samples(mp4fh, track); - if (num_samples > (long)(G_MAXINT / sizeof(float))) { - g_warning("Integer overflow.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - return; - } - - file_time = 0.0; - - seek_table = input_stream->seekable - ? g_malloc(sizeof(float) * num_samples) - : NULL; - - decoder_initialized(mpd_decoder, &audio_format, - input_stream->seekable, - total_time); - - for (sample_id = 0; - sample_id < num_samples && cmd != DECODE_COMMAND_STOP; - sample_id++) { - if (cmd == DECODE_COMMAND_SEEK) { - assert(seek_table != NULL); - - seeking = true; - seek_where = decoder_seek_where(mpd_decoder); - } - - if (seeking && seek_table_end > 1 && - seek_table[seek_table_end] >= seek_where) { - int i = 2; - - assert(seek_table != NULL); - - while (seek_table[i] < seek_where) - i++; - sample_id = i - 1; - file_time = seek_table[sample_id]; - } - - dur = mp4ff_get_sample_duration(mp4fh, track, sample_id); - offset = mp4ff_get_sample_offset(mp4fh, track, sample_id); - - if (seek_table != NULL && sample_id > seek_table_end) { - seek_table[sample_id] = file_time; - seek_table_end = sample_id; - } - - if (sample_id == 0) - dur = 0; - if (offset > dur) - dur = 0; - else - dur -= offset; - file_time += ((float)dur) / scale; - - if (seeking && file_time >= seek_where) - seek_position_found = true; - - if (seeking && seek_position_found) { - seek_position_found = false; - seeking = 0; - decoder_command_finished(mpd_decoder); - } - - if (seeking) - continue; - - if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer, - &mp4_buffer_size) == 0) - break; - -#ifdef HAVE_FAAD_BUFLEN_FUNCS - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer, - mp4_buffer_size); -#else - sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer); -#endif - - free(mp4_buffer); - - if (frame_info.error > 0) { - g_warning("faad2 error: %s\n", - faacDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - g_warning("channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - if (frame_info.samplerate != audio_format.sample_rate) { - g_warning("sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } -#endif - - if (audio_format.channels * (unsigned long)(dur + offset) > frame_info.samples) { - dur = frame_info.samples / audio_format.channels; - offset = 0; - } - - sample_count = (unsigned long)(dur * audio_format.channels); - - if (sample_count > 0) { - initial = 0; - bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * scale / - frame_info.samples / 1000 + 0.5; - } - - sample_buffer_length = sample_count * 2; - - sample_buffer += offset * audio_format.channels * 2; - - cmd = decoder_data(mpd_decoder, input_stream, - sample_buffer, sample_buffer_length, - bit_rate); - } - - g_free(seek_table); - faacDecClose(decoder); - mp4ff_close(mp4fh); -} - -static struct tag * -mp4_stream_tag(struct input_stream *is) -{ - struct tag *ret = NULL; - struct mp4_context ctx = { - .decoder = NULL, - .input_stream = is, - }; - mp4ff_callback_t callback = { - .read = mp4_read, - .seek = mp4_seek, - .user_data = &ctx, - }; - mp4ff_t *mp4fh; - int32_t track; - int32_t file_time; - int32_t scale; - int i; - - mp4fh = mp4ff_open_read(&callback); - if (mp4fh == NULL) - return NULL; - - track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); - if (track < 0) { - mp4ff_close(mp4fh); - return NULL; - } - - ret = tag_new(); - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - if (scale < 0) { - mp4ff_close(mp4fh); - tag_free(ret); - return NULL; - } - ret->time = ((float)file_time) / scale + 0.5; - - for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { - char *item; - char *value; - - mp4ff_meta_get_by_index(mp4fh, i, &item, &value); - - if (0 == g_ascii_strcasecmp("artist", item)) { - tag_add_item(ret, TAG_ARTIST, value); - } else if (0 == g_ascii_strcasecmp("title", item)) { - tag_add_item(ret, TAG_TITLE, value); - } else if (0 == g_ascii_strcasecmp("album", item)) { - tag_add_item(ret, TAG_ALBUM, value); - } else if (0 == g_ascii_strcasecmp("track", item)) { - tag_add_item(ret, TAG_TRACK, value); - } else if (0 == g_ascii_strcasecmp("disc", item)) { - /* Is that the correct id? */ - tag_add_item(ret, TAG_DISC, value); - } else if (0 == g_ascii_strcasecmp("genre", item)) { - tag_add_item(ret, TAG_GENRE, value); - } else if (0 == g_ascii_strcasecmp("date", item)) { - tag_add_item(ret, TAG_DATE, value); - } else if (0 == g_ascii_strcasecmp("writer", item)) { - tag_add_item(ret, TAG_COMPOSER, value); - } - - free(item); - free(value); - } - - mp4ff_close(mp4fh); - - return ret; -} - -static const char *const mp4_suffixes[] = { "m4a", "mp4", NULL }; -static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; - -const struct decoder_plugin mp4ff_decoder_plugin = { - .name = "mp4", - .stream_decode = mp4_decode, - .stream_tag = mp4_stream_tag, - .suffixes = mp4_suffixes, - .mime_types = mp4_mime_types, -}; diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c new file mode 100644 index 000000000..92f0cad84 --- /dev/null +++ b/src/decoder/mpcdec_decoder_plugin.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" + +#ifdef MPC_IS_OLD_API +#include +#else +#include +#endif + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mpcdec" + +struct mpc_decoder_data { + struct input_stream *is; + struct decoder *decoder; +}; + +#ifdef MPC_IS_OLD_API +#define cb_first_arg void *vdata +#define cb_data vdata +#else +#define cb_first_arg mpc_reader *reader +#define cb_data reader->data +#endif + +static mpc_int32_t +mpc_read_cb(cb_first_arg, void *ptr, mpc_int32_t size) +{ + struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; + + return decoder_read(data->decoder, data->is, ptr, size); +} + +static mpc_bool_t +mpc_seek_cb(cb_first_arg, mpc_int32_t offset) +{ + struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; + + return input_stream_seek(data->is, offset, SEEK_SET, NULL); +} + +static mpc_int32_t +mpc_tell_cb(cb_first_arg) +{ + struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; + + return (long)(data->is->offset); +} + +static mpc_bool_t +mpc_canseek_cb(cb_first_arg) +{ + struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; + + return data->is->seekable; +} + +static mpc_int32_t +mpc_getsize_cb(cb_first_arg) +{ + struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; + + return data->is->size; +} + +/* this _looks_ performance-critical, don't de-inline -- eric */ +static inline int32_t +mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample) +{ + /* only doing 16-bit audio for now */ + int32_t val; + + enum { + bits = 24, + }; + + const int clip_min = -1 << (bits - 1); + const int clip_max = (1 << (bits - 1)) - 1; + +#ifdef MPC_FIXED_POINT + const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT; + + if (shift < 0) + val = sample << -shift; + else + val = sample << shift; +#else + const int float_scale = 1 << (bits - 1); + + val = sample * float_scale; +#endif + + if (val < clip_min) + val = clip_min; + else if (val > clip_max) + val = clip_max; + + return val; +} + +static void +mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src, + unsigned num_samples) +{ + while (num_samples-- > 0) + *dest++ = mpc_to_mpd_sample(*src++); +} + +static void +mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) +{ +#ifdef MPC_IS_OLD_API + mpc_decoder decoder; +#else + mpc_demux *demux; + mpc_frame_info frame; + mpc_status status; +#endif + mpc_reader reader; + mpc_streaminfo info; + GError *error = NULL; + struct audio_format audio_format; + + struct mpc_decoder_data data; + + MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; + + mpc_uint32_t ret; + int32_t chunk[G_N_ELEMENTS(sample_buffer)]; + long bit_rate = 0; + mpc_uint32_t vbr_update_acc; + mpc_uint32_t vbr_update_bits; + struct replay_gain_info *replay_gain_info = NULL; + enum decoder_command cmd = DECODE_COMMAND_NONE; + + data.is = is; + data.decoder = mpd_decoder; + + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + +#ifdef MPC_IS_OLD_API + mpc_streaminfo_init(&info); + + if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) { + if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) + g_warning("Not a valid musepack stream\n"); + return; + } + + mpc_decoder_setup(&decoder, &reader); + + if (!mpc_decoder_initialize(&decoder, &info)) { + if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) + g_warning("Not a valid musepack stream\n"); + return; + } +#else + demux = mpc_demux_init(&reader); + if (demux == NULL) { + if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) + g_warning("Not a valid musepack stream"); + return; + } + + mpc_demux_get_info(demux, &info); +#endif + + if (!audio_format_init_checked(&audio_format, info.sample_freq, + SAMPLE_FORMAT_S24_P32, + info.channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); +#ifndef MPC_IS_OLD_API + mpc_demux_exit(demux); +#endif + return; + } + + replay_gain_info = replay_gain_info_new(); + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01; + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0; + replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01; + replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0; + decoder_replay_gain(mpd_decoder, replay_gain_info); + replay_gain_info_free(replay_gain_info); + + decoder_initialized(mpd_decoder, &audio_format, + is->seekable, + mpc_streaminfo_get_length(&info)); + + do { + if (cmd == DECODE_COMMAND_SEEK) { + mpc_int64_t where = decoder_seek_where(mpd_decoder) * + audio_format.sample_rate; + bool success; + +#ifdef MPC_IS_OLD_API + success = mpc_decoder_seek_sample(&decoder, where); +#else + success = mpc_demux_seek_sample(demux, where) + == MPC_STATUS_OK; +#endif + if (success) + decoder_command_finished(mpd_decoder); + else + decoder_seek_error(mpd_decoder); + } + + vbr_update_acc = 0; + vbr_update_bits = 0; + +#ifdef MPC_IS_OLD_API + ret = mpc_decoder_decode(&decoder, sample_buffer, + &vbr_update_acc, &vbr_update_bits); + if (ret == 0 || ret == (mpc_uint32_t)-1) + break; +#else + frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; + status = mpc_demux_decode(demux, &frame); + if (status != MPC_STATUS_OK) { + g_warning("Failed to decode sample"); + break; + } + + if (frame.bits == -1) + break; + + ret = frame.samples; +#endif + + ret *= info.channels; + + mpc_to_mpd_buffer(chunk, sample_buffer, ret); + + bit_rate = vbr_update_bits * audio_format.sample_rate + / 1152 / 1000; + + cmd = decoder_data(mpd_decoder, is, + chunk, ret * sizeof(chunk[0]), + bit_rate); + } while (cmd != DECODE_COMMAND_STOP); + +#ifndef MPC_IS_OLD_API + mpc_demux_exit(demux); +#endif +} + +static float +mpcdec_get_file_duration(struct input_stream *is) +{ + float total_time = -1; + + mpc_reader reader; +#ifndef MPC_IS_OLD_API + mpc_demux *demux; +#endif + mpc_streaminfo info; + struct mpc_decoder_data data; + + data.is = is; + data.decoder = NULL; + + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + +#ifdef MPC_IS_OLD_API + mpc_streaminfo_init(&info); + + if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) + return -1; +#else + demux = mpc_demux_init(&reader); + if (demux == NULL) + return -1; + + mpc_demux_get_info(demux, &info); + mpc_demux_exit(demux); +#endif + + total_time = mpc_streaminfo_get_length(&info); + + return total_time; +} + +static struct tag * +mpcdec_stream_tag(struct input_stream *is) +{ + float total_time = mpcdec_get_file_duration(is); + struct tag *tag; + + if (total_time < 0) + return NULL; + + tag = tag_new(); + tag->time = total_time; + return tag; +} + +static const char *const mpcdec_suffixes[] = { "mpc", NULL }; + +const struct decoder_plugin mpcdec_decoder_plugin = { + .name = "mpcdec", + .stream_decode = mpcdec_decode, + .stream_tag = mpcdec_stream_tag, + .suffixes = mpcdec_suffixes, +}; diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_plugin.c deleted file mode 100644 index 92f0cad84..000000000 --- a/src/decoder/mpcdec_plugin.c +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" - -#ifdef MPC_IS_OLD_API -#include -#else -#include -#endif - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mpcdec" - -struct mpc_decoder_data { - struct input_stream *is; - struct decoder *decoder; -}; - -#ifdef MPC_IS_OLD_API -#define cb_first_arg void *vdata -#define cb_data vdata -#else -#define cb_first_arg mpc_reader *reader -#define cb_data reader->data -#endif - -static mpc_int32_t -mpc_read_cb(cb_first_arg, void *ptr, mpc_int32_t size) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return decoder_read(data->decoder, data->is, ptr, size); -} - -static mpc_bool_t -mpc_seek_cb(cb_first_arg, mpc_int32_t offset) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return input_stream_seek(data->is, offset, SEEK_SET, NULL); -} - -static mpc_int32_t -mpc_tell_cb(cb_first_arg) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return (long)(data->is->offset); -} - -static mpc_bool_t -mpc_canseek_cb(cb_first_arg) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return data->is->seekable; -} - -static mpc_int32_t -mpc_getsize_cb(cb_first_arg) -{ - struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - - return data->is->size; -} - -/* this _looks_ performance-critical, don't de-inline -- eric */ -static inline int32_t -mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample) -{ - /* only doing 16-bit audio for now */ - int32_t val; - - enum { - bits = 24, - }; - - const int clip_min = -1 << (bits - 1); - const int clip_max = (1 << (bits - 1)) - 1; - -#ifdef MPC_FIXED_POINT - const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT; - - if (shift < 0) - val = sample << -shift; - else - val = sample << shift; -#else - const int float_scale = 1 << (bits - 1); - - val = sample * float_scale; -#endif - - if (val < clip_min) - val = clip_min; - else if (val > clip_max) - val = clip_max; - - return val; -} - -static void -mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src, - unsigned num_samples) -{ - while (num_samples-- > 0) - *dest++ = mpc_to_mpd_sample(*src++); -} - -static void -mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) -{ -#ifdef MPC_IS_OLD_API - mpc_decoder decoder; -#else - mpc_demux *demux; - mpc_frame_info frame; - mpc_status status; -#endif - mpc_reader reader; - mpc_streaminfo info; - GError *error = NULL; - struct audio_format audio_format; - - struct mpc_decoder_data data; - - MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; - - mpc_uint32_t ret; - int32_t chunk[G_N_ELEMENTS(sample_buffer)]; - long bit_rate = 0; - mpc_uint32_t vbr_update_acc; - mpc_uint32_t vbr_update_bits; - struct replay_gain_info *replay_gain_info = NULL; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - data.is = is; - data.decoder = mpd_decoder; - - reader.read = mpc_read_cb; - reader.seek = mpc_seek_cb; - reader.tell = mpc_tell_cb; - reader.get_size = mpc_getsize_cb; - reader.canseek = mpc_canseek_cb; - reader.data = &data; - -#ifdef MPC_IS_OLD_API - mpc_streaminfo_init(&info); - - if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) - g_warning("Not a valid musepack stream\n"); - return; - } - - mpc_decoder_setup(&decoder, &reader); - - if (!mpc_decoder_initialize(&decoder, &info)) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) - g_warning("Not a valid musepack stream\n"); - return; - } -#else - demux = mpc_demux_init(&reader); - if (demux == NULL) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) - g_warning("Not a valid musepack stream"); - return; - } - - mpc_demux_get_info(demux, &info); -#endif - - if (!audio_format_init_checked(&audio_format, info.sample_freq, - SAMPLE_FORMAT_S24_P32, - info.channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); -#ifndef MPC_IS_OLD_API - mpc_demux_exit(demux); -#endif - return; - } - - replay_gain_info = replay_gain_info_new(); - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01; - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0; - decoder_replay_gain(mpd_decoder, replay_gain_info); - replay_gain_info_free(replay_gain_info); - - decoder_initialized(mpd_decoder, &audio_format, - is->seekable, - mpc_streaminfo_get_length(&info)); - - do { - if (cmd == DECODE_COMMAND_SEEK) { - mpc_int64_t where = decoder_seek_where(mpd_decoder) * - audio_format.sample_rate; - bool success; - -#ifdef MPC_IS_OLD_API - success = mpc_decoder_seek_sample(&decoder, where); -#else - success = mpc_demux_seek_sample(demux, where) - == MPC_STATUS_OK; -#endif - if (success) - decoder_command_finished(mpd_decoder); - else - decoder_seek_error(mpd_decoder); - } - - vbr_update_acc = 0; - vbr_update_bits = 0; - -#ifdef MPC_IS_OLD_API - ret = mpc_decoder_decode(&decoder, sample_buffer, - &vbr_update_acc, &vbr_update_bits); - if (ret == 0 || ret == (mpc_uint32_t)-1) - break; -#else - frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; - status = mpc_demux_decode(demux, &frame); - if (status != MPC_STATUS_OK) { - g_warning("Failed to decode sample"); - break; - } - - if (frame.bits == -1) - break; - - ret = frame.samples; -#endif - - ret *= info.channels; - - mpc_to_mpd_buffer(chunk, sample_buffer, ret); - - bit_rate = vbr_update_bits * audio_format.sample_rate - / 1152 / 1000; - - cmd = decoder_data(mpd_decoder, is, - chunk, ret * sizeof(chunk[0]), - bit_rate); - } while (cmd != DECODE_COMMAND_STOP); - -#ifndef MPC_IS_OLD_API - mpc_demux_exit(demux); -#endif -} - -static float -mpcdec_get_file_duration(struct input_stream *is) -{ - float total_time = -1; - - mpc_reader reader; -#ifndef MPC_IS_OLD_API - mpc_demux *demux; -#endif - mpc_streaminfo info; - struct mpc_decoder_data data; - - data.is = is; - data.decoder = NULL; - - reader.read = mpc_read_cb; - reader.seek = mpc_seek_cb; - reader.tell = mpc_tell_cb; - reader.get_size = mpc_getsize_cb; - reader.canseek = mpc_canseek_cb; - reader.data = &data; - -#ifdef MPC_IS_OLD_API - mpc_streaminfo_init(&info); - - if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) - return -1; -#else - demux = mpc_demux_init(&reader); - if (demux == NULL) - return -1; - - mpc_demux_get_info(demux, &info); - mpc_demux_exit(demux); -#endif - - total_time = mpc_streaminfo_get_length(&info); - - return total_time; -} - -static struct tag * -mpcdec_stream_tag(struct input_stream *is) -{ - float total_time = mpcdec_get_file_duration(is); - struct tag *tag; - - if (total_time < 0) - return NULL; - - tag = tag_new(); - tag->time = total_time; - return tag; -} - -static const char *const mpcdec_suffixes[] = { "mpc", NULL }; - -const struct decoder_plugin mpcdec_decoder_plugin = { - .name = "mpcdec", - .stream_decode = mpcdec_decode, - .stream_tag = mpcdec_stream_tag, - .suffixes = mpcdec_suffixes, -}; diff --git a/src/decoder/oggflac_decoder_plugin.c b/src/decoder/oggflac_decoder_plugin.c new file mode 100644 index 000000000..502448a12 --- /dev/null +++ b/src/decoder/oggflac_decoder_plugin.c @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * OggFLAC support (half-stolen from flac_plugin.c :)) + */ + +#include "config.h" /* must be first for large file support */ +#include "_flac_common.h" +#include "_ogg_common.h" +#include "flac_metadata.h" + +#include +#include +#include +#include + +static void oggflac_cleanup(OggFLAC__SeekableStreamDecoder * decoder) +{ + if (decoder) + OggFLAC__seekable_stream_decoder_delete(decoder); +} + +static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(G_GNUC_UNUSED const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__byte buf[], + unsigned *bytes, + void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + size_t r; + + r = decoder_read(data->decoder, data->input_stream, + (void *)buf, *bytes); + *bytes = r; + + if (r == 0 && !input_stream_eof(data->input_stream) && + decoder_get_command(data->decoder) == DECODE_COMMAND_NONE) + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; + + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 offset, + void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) + return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; + + return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(G_GNUC_UNUSED const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 * + offset, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + *offset = (long)(data->input_stream->offset); + + return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(G_GNUC_UNUSED const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 * + length, + void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (data->input_stream->size < 0) + return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR; + + *length = (size_t) (data->input_stream->size); + + return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool of_EOF_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, + void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && + decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || + input_stream_eof(data->input_stream); +} + +static void of_error_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("oggflac", status, (struct flac_data *) fdata); +} + +static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state) +{ + switch (state) { + case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + g_warning("oggflac allocation error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + g_warning("oggflac read error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + g_warning("oggflac seek error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + g_warning("oggflac seekable stream error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + g_warning("oggflac decoder already initialized\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + g_warning("invalid oggflac callback\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + g_warning("oggflac decoder uninitialized\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_OK: + case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + break; + } +} + +static FLAC__StreamDecoderWriteStatus +oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder, + const FLAC__Frame *frame, const FLAC__int32 *const buf[], + void *vdata) +{ + struct flac_data *data = (struct flac_data *) vdata; + + return flac_common_write(data, frame, buf, 0); +} + +/* used by TagDup */ +static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, + const FLAC__StreamMetadata * block, void *vdata) +{ + struct flac_data *data = (struct flac_data *) vdata; + + assert(data->tag != NULL); + + flac_tag_apply_metadata(data->tag, NULL, block); +} + +/* used by decode */ +static void of_metadata_decode_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * dec, + const FLAC__StreamMetadata * block, + void *vdata) +{ + flac_metadata_common_cb(block, (struct flac_data *) vdata); +} + +static OggFLAC__SeekableStreamDecoder * +full_decoder_init_and_read_metadata(struct flac_data *data, + unsigned int metadata_only) +{ + OggFLAC__SeekableStreamDecoder *decoder = NULL; + unsigned int s = 1; + + if (!(decoder = OggFLAC__seekable_stream_decoder_new())) + return NULL; + + if (metadata_only) { + s &= OggFLAC__seekable_stream_decoder_set_metadata_callback + (decoder, of_metadata_dup_cb); + s &= OggFLAC__seekable_stream_decoder_set_metadata_respond + (decoder, FLAC__METADATA_TYPE_STREAMINFO); + } else { + s &= OggFLAC__seekable_stream_decoder_set_metadata_callback + (decoder, of_metadata_decode_cb); + } + + s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder, + of_read_cb); + s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder, + of_seek_cb); + s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder, + of_tell_cb); + s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder, + of_length_cb); + s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder, + of_EOF_cb); + s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder, + oggflac_write_cb); + s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder, + FLAC__METADATA_TYPE_VORBIS_COMMENT); + s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder, + of_error_cb); + s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder, + (void *)data); + + if (!s) { + g_warning("oggflac problem before init()\n"); + goto fail; + } + if (OggFLAC__seekable_stream_decoder_init(decoder) != + OggFLAC__SEEKABLE_STREAM_DECODER_OK) { + g_warning("oggflac problem doing init()\n"); + goto fail; + } + if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata + (decoder)) { + g_warning("oggflac problem reading metadata\n"); + goto fail; + } + + return decoder; + +fail: + oggflacPrintErroredState(OggFLAC__seekable_stream_decoder_get_state + (decoder)); + OggFLAC__seekable_stream_decoder_delete(decoder); + return NULL; +} + +/* public functions: */ +static struct tag * +oggflac_stream_tag(struct input_stream *is) +{ + OggFLAC__SeekableStreamDecoder *decoder; + struct flac_data data; + struct tag *tag; + + if (ogg_stream_type_detect(is) != FLAC) + return NULL; + + /* rewind the stream, because ogg_stream_type_detect() has + moved it */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + flac_data_init(&data, NULL, is); + + data.tag = tag_new(); + + /* errors here won't matter, + * data.tag will be set or unset, that's all we care about */ + decoder = full_decoder_init_and_read_metadata(&data, 1); + + oggflac_cleanup(decoder); + + if (tag_is_defined(data.tag)) { + tag = data.tag; + data.tag = NULL; + } else + tag = NULL; + + flac_data_deinit(&data); + + return tag; +} + +static void +oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) +{ + OggFLAC__SeekableStreamDecoder *decoder = NULL; + struct flac_data data; + struct audio_format audio_format; + + if (ogg_stream_type_detect(input_stream) != FLAC) + return; + + /* rewind the stream, because ogg_stream_type_detect() has + moved it */ + input_stream_seek(input_stream, 0, SEEK_SET, NULL); + + flac_data_init(&data, mpd_decoder, input_stream); + + if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) { + goto fail; + } + + if (!flac_data_get_audio_format(&data, &audio_format)) + goto fail; + + decoder_initialized(mpd_decoder, &audio_format, + input_stream->seekable, + (float)data.stream_info.total_samples / + (float)data.stream_info.sample_rate); + + while (true) { + OggFLAC__seekable_stream_decoder_process_single(decoder); + if (OggFLAC__seekable_stream_decoder_get_state(decoder) != + OggFLAC__SEEKABLE_STREAM_DECODER_OK) { + break; + } + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) * + data.stream_info.sample_rate; + if (OggFLAC__seekable_stream_decoder_seek_absolute + (decoder, seek_sample)) { + data.next_frame = seek_sample; + data.position = 0; + decoder_command_finished(mpd_decoder); + } else + decoder_seek_error(mpd_decoder); + } + } + + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { + oggflacPrintErroredState + (OggFLAC__seekable_stream_decoder_get_state(decoder)); + OggFLAC__seekable_stream_decoder_finish(decoder); + } + +fail: + oggflac_cleanup(decoder); + flac_data_deinit(&data); +} + +static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/x-ogg", + "audio/x-flac+ogg", + NULL +}; + +const struct decoder_plugin oggflac_decoder_plugin = { + .name = "oggflac", + .stream_decode = oggflac_decode, + .stream_tag = oggflac_stream_tag, + .suffixes = oggflac_suffixes, + .mime_types = oggflac_mime_types +}; diff --git a/src/decoder/oggflac_plugin.c b/src/decoder/oggflac_plugin.c deleted file mode 100644 index 502448a12..000000000 --- a/src/decoder/oggflac_plugin.c +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * OggFLAC support (half-stolen from flac_plugin.c :)) - */ - -#include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "_ogg_common.h" -#include "flac_metadata.h" - -#include -#include -#include -#include - -static void oggflac_cleanup(OggFLAC__SeekableStreamDecoder * decoder) -{ - if (decoder) - OggFLAC__seekable_stream_decoder_delete(decoder); -} - -static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__byte buf[], - unsigned *bytes, - void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0 && !input_stream_eof(data->input_stream) && - decoder_get_command(data->decoder) == DECODE_COMMAND_NONE) - return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; - - return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; -} - -static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__uint64 offset, - void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) - return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; - - return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK; -} - -static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__uint64 * - offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - *offset = (long)(data->input_stream->offset); - - return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; -} - -static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__uint64 * - length, - void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR; - - *length = (size_t) (data->input_stream->size); - - return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; -} - -static FLAC__bool of_EOF_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, - void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_eof(data->input_stream); -} - -static void of_error_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb("oggflac", status, (struct flac_data *) fdata); -} - -static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state) -{ - switch (state) { - case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - g_warning("oggflac allocation error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - g_warning("oggflac read error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - g_warning("oggflac seek error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - g_warning("oggflac seekable stream error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - g_warning("oggflac decoder already initialized\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - g_warning("invalid oggflac callback\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - g_warning("oggflac decoder uninitialized\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_OK: - case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - break; - } -} - -static FLAC__StreamDecoderWriteStatus -oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder, - const FLAC__Frame *frame, const FLAC__int32 *const buf[], - void *vdata) -{ - struct flac_data *data = (struct flac_data *) vdata; - - return flac_common_write(data, frame, buf, 0); -} - -/* used by TagDup */ -static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, - const FLAC__StreamMetadata * block, void *vdata) -{ - struct flac_data *data = (struct flac_data *) vdata; - - assert(data->tag != NULL); - - flac_tag_apply_metadata(data->tag, NULL, block); -} - -/* used by decode */ -static void of_metadata_decode_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * dec, - const FLAC__StreamMetadata * block, - void *vdata) -{ - flac_metadata_common_cb(block, (struct flac_data *) vdata); -} - -static OggFLAC__SeekableStreamDecoder * -full_decoder_init_and_read_metadata(struct flac_data *data, - unsigned int metadata_only) -{ - OggFLAC__SeekableStreamDecoder *decoder = NULL; - unsigned int s = 1; - - if (!(decoder = OggFLAC__seekable_stream_decoder_new())) - return NULL; - - if (metadata_only) { - s &= OggFLAC__seekable_stream_decoder_set_metadata_callback - (decoder, of_metadata_dup_cb); - s &= OggFLAC__seekable_stream_decoder_set_metadata_respond - (decoder, FLAC__METADATA_TYPE_STREAMINFO); - } else { - s &= OggFLAC__seekable_stream_decoder_set_metadata_callback - (decoder, of_metadata_decode_cb); - } - - s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder, - of_read_cb); - s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder, - of_seek_cb); - s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder, - of_tell_cb); - s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder, - of_length_cb); - s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder, - of_EOF_cb); - s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder, - oggflac_write_cb); - s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder, - FLAC__METADATA_TYPE_VORBIS_COMMENT); - s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder, - of_error_cb); - s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder, - (void *)data); - - if (!s) { - g_warning("oggflac problem before init()\n"); - goto fail; - } - if (OggFLAC__seekable_stream_decoder_init(decoder) != - OggFLAC__SEEKABLE_STREAM_DECODER_OK) { - g_warning("oggflac problem doing init()\n"); - goto fail; - } - if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata - (decoder)) { - g_warning("oggflac problem reading metadata\n"); - goto fail; - } - - return decoder; - -fail: - oggflacPrintErroredState(OggFLAC__seekable_stream_decoder_get_state - (decoder)); - OggFLAC__seekable_stream_decoder_delete(decoder); - return NULL; -} - -/* public functions: */ -static struct tag * -oggflac_stream_tag(struct input_stream *is) -{ - OggFLAC__SeekableStreamDecoder *decoder; - struct flac_data data; - struct tag *tag; - - if (ogg_stream_type_detect(is) != FLAC) - return NULL; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_seek(is, 0, SEEK_SET, NULL); - - flac_data_init(&data, NULL, is); - - data.tag = tag_new(); - - /* errors here won't matter, - * data.tag will be set or unset, that's all we care about */ - decoder = full_decoder_init_and_read_metadata(&data, 1); - - oggflac_cleanup(decoder); - - if (tag_is_defined(data.tag)) { - tag = data.tag; - data.tag = NULL; - } else - tag = NULL; - - flac_data_deinit(&data); - - return tag; -} - -static void -oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) -{ - OggFLAC__SeekableStreamDecoder *decoder = NULL; - struct flac_data data; - struct audio_format audio_format; - - if (ogg_stream_type_detect(input_stream) != FLAC) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_seek(input_stream, 0, SEEK_SET, NULL); - - flac_data_init(&data, mpd_decoder, input_stream); - - if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) { - goto fail; - } - - if (!flac_data_get_audio_format(&data, &audio_format)) - goto fail; - - decoder_initialized(mpd_decoder, &audio_format, - input_stream->seekable, - (float)data.stream_info.total_samples / - (float)data.stream_info.sample_rate); - - while (true) { - OggFLAC__seekable_stream_decoder_process_single(decoder); - if (OggFLAC__seekable_stream_decoder_get_state(decoder) != - OggFLAC__SEEKABLE_STREAM_DECODER_OK) { - break; - } - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) * - data.stream_info.sample_rate; - if (OggFLAC__seekable_stream_decoder_seek_absolute - (decoder, seek_sample)) { - data.next_frame = seek_sample; - data.position = 0; - decoder_command_finished(mpd_decoder); - } else - decoder_seek_error(mpd_decoder); - } - } - - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { - oggflacPrintErroredState - (OggFLAC__seekable_stream_decoder_get_state(decoder)); - OggFLAC__seekable_stream_decoder_finish(decoder); - } - -fail: - oggflac_cleanup(decoder); - flac_data_deinit(&data); -} - -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; -static const char *const oggflac_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/x-ogg", - "audio/x-flac+ogg", - NULL -}; - -const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .stream_decode = oggflac_decode, - .stream_tag = oggflac_stream_tag, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -}; diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx new file mode 100644 index 000000000..a2eb21ae4 --- /dev/null +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "../decoder_api.h" +} + +#include +#include +#include + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "sidplay" + +#define SUBTUNE_PREFIX "tune_" + +static GPatternSpec *path_with_subtune; +static const char *songlength_file; +static GKeyFile *songlength_database; + +static bool all_files_are_containers; +static unsigned default_songlength; + +static bool filter_setting; + +static GKeyFile * +sidplay_load_songlength_db(const char *path) +{ + GError *error = NULL; + gchar *data; + gsize size; + + if (!g_file_get_contents(path, &data, &size, &error)) { + g_warning("unable to read songlengths file %s: %s", + path, error->message); + g_error_free(error); + return NULL; + } + + /* replace any ; comment characters with # */ + for (gsize i = 0; i < size; i++) + if (data[i] == ';') + data[i] = '#'; + + GKeyFile *db = g_key_file_new(); + bool success = g_key_file_load_from_data(db, data, size, + G_KEY_FILE_NONE, &error); + g_free(data); + if (!success) { + g_warning("unable to parse songlengths file %s: %s", + path, error->message); + g_error_free(error); + g_key_file_free(db); + return NULL; + } + + g_key_file_set_list_separator(db, ' '); + return db; +} + +static bool +sidplay_init(const struct config_param *param) +{ + /* read the songlengths database file */ + songlength_file=config_get_block_string(param, + "songlength_database", NULL); + if (songlength_file != NULL) + songlength_database = sidplay_load_songlength_db(songlength_file); + + default_songlength=config_get_block_unsigned(param, + "default_songlength", 0); + + all_files_are_containers=config_get_block_bool(param, + "all_files_are_containers", true); + + path_with_subtune=g_pattern_spec_new( + "*/" SUBTUNE_PREFIX "???.sid"); + + filter_setting=config_get_block_bool(param, "filter", true); + + return true; +} + +void +sidplay_finish() +{ + g_pattern_spec_free(path_with_subtune); + + if(songlength_database) + g_key_file_free(songlength_database); +} + +/** + * returns the file path stripped of any /tune_xxx.sid subtune + * suffix + */ +static char * +get_container_name(const char *path_fs) +{ + char *path_container=g_strdup(path_fs); + + if(!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, NULL)) + return path_container; + + char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if(ptr) *ptr='\0'; + + return path_container; +} + +/** + * returns tune number from file.sid/tune_xxx.sid style path or 1 if + * no subtune is appended + */ +static int +get_song_num(const char *path_fs) +{ + if(g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, NULL)) { + char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + if(!sub) return 1; + + sub+=strlen("/" SUBTUNE_PREFIX); + int song_num=strtol(sub, NULL, 10); + + if (errno == EINVAL) + return 1; + else + return song_num; + } else + return 1; +} + +/* get the song length in seconds */ +static int +get_song_length(const char *path_fs) +{ + if (songlength_database == NULL) + return -1; + + gchar *sid_file=get_container_name(path_fs); + SidTuneMod tune(sid_file); + g_free(sid_file); + if(!tune) { + g_warning("failed to load file for calculating md5 sum"); + return -1; + } + char md5sum[SIDTUNE_MD5_LENGTH+1]; + tune.createMD5(md5sum); + + int song_num=get_song_num(path_fs); + + gsize num_items; + gchar **values=g_key_file_get_string_list(songlength_database, + "Database", md5sum, &num_items, NULL); + if(!values || song_num>num_items) { + g_strfreev(values); + return -1; + } + + int minutes=strtol(values[song_num-1], NULL, 10); + if(errno==EINVAL) minutes=0; + + int seconds; + char *ptr=strchr(values[song_num-1], ':'); + if(ptr) { + seconds=strtol(ptr+1, NULL, 10); + if(errno==EINVAL) seconds=0; + } else + seconds=0; + + g_strfreev(values); + + return (minutes*60)+seconds; +} + +static void +sidplay_file_decode(struct decoder *decoder, const char *path_fs) +{ + int ret; + + /* load the tune */ + + char *path_container=get_container_name(path_fs); + SidTune tune(path_container, NULL, true); + g_free(path_container); + if (!tune) { + g_warning("failed to load file"); + return; + } + + int song_num=get_song_num(path_fs); + tune.selectSong(song_num); + + int song_len=get_song_length(path_fs); + if(song_len==-1) song_len=default_songlength; + + /* initialize the player */ + + sidplay2 player; + int iret = player.load(&tune); + if (iret != 0) { + g_warning("sidplay2.load() failed: %s", player.error()); + return; + } + + /* initialize the builder */ + + ReSIDBuilder builder("ReSID"); + if (!builder) { + g_warning("failed to initialize ReSIDBuilder"); + return; + } + + builder.create(player.info().maxsids); + if (!builder) { + g_warning("ReSIDBuilder.create() failed"); + return; + } + + builder.filter(filter_setting); + if (!builder) { + g_warning("ReSIDBuilder.filter() failed"); + return; + } + + /* configure the player */ + + sid2_config_t config = player.config(); + + config.clockDefault = SID2_CLOCK_PAL; + config.clockForced = true; + config.clockSpeed = SID2_CLOCK_CORRECT; + config.frequency = 48000; + config.optimisation = SID2_DEFAULT_OPTIMISATION; + config.playback = sid2_stereo; + config.precision = 16; + config.sidDefault = SID2_MOS6581; + config.sidEmulation = &builder; + config.sidModel = SID2_MODEL_CORRECT; + config.sidSamples = true; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + config.sampleFormat = SID2_LITTLE_SIGNED; +#else + config.sampleFormat = SID2_BIG_SIGNED; +#endif + + iret = player.config(config); + if (iret != 0) { + g_warning("sidplay2.config() failed: %s", player.error()); + return; + } + + /* initialize the MPD decoder */ + + struct audio_format audio_format; + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, true, (float)song_len); + + /* .. and play */ + + const unsigned timebase = player.timebase(); + song_len *= timebase; + + enum decoder_command cmd; + do { + char buffer[4096]; + size_t nbytes; + + nbytes = player.play(buffer, sizeof(buffer)); + if (nbytes == 0) + break; + + decoder_timestamp(decoder, (double)player.time() / timebase); + + cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); + + if(cmd==DECODE_COMMAND_SEEK) { + unsigned data_time = player.time(); + unsigned target_time = (unsigned) + (decoder_seek_where(decoder) * timebase); + + /* can't rewind so return to zero and seek forward */ + if(target_time 0 && player.time() >= song_len) + break; + + } while (cmd != DECODE_COMMAND_STOP); +} + +static struct tag * +sidplay_tag_dup(const char *path_fs) +{ + int song_num=get_song_num(path_fs); + char *path_container=get_container_name(path_fs); + + SidTune tune(path_container, NULL, true); + g_free(path_container); + if (!tune) + return NULL; + + const SidTuneInfo &info = tune.getInfo(); + struct tag *tag = tag_new(); + + /* title */ + const char *title; + if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL) + title=info.infoString[0]; + else + title=""; + + if(info.songs>1) { + char *tag_title=g_strdup_printf("%s (%d/%d)", + title, song_num, info.songs); + tag_add_item(tag, TAG_TITLE, tag_title); + g_free(tag_title); + } else + tag_add_item(tag, TAG_TITLE, title); + + /* artist */ + if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) + tag_add_item(tag, TAG_ARTIST, info.infoString[1]); + + /* track */ + char *track=g_strdup_printf("%d", song_num); + tag_add_item(tag, TAG_TRACK, track); + g_free(track); + + /* time */ + int song_len=get_song_length(path_fs); + if(song_len!=-1) tag->time=song_len; + + return tag; +} + +static char * +sidplay_container_scan(const char *path_fs, const unsigned int tnum) +{ + SidTune tune(path_fs, NULL, true); + if (!tune) + return NULL; + + const SidTuneInfo &info=tune.getInfo(); + + /* Don't treat sids containing a single tune + as containers */ + if(!all_files_are_containers && info.songs<2) + return NULL; + + /* Construct container/tune path names, eg. + Delta.sid/tune_001.sid */ + if(tnum<=info.songs) { + char *subtune= g_strdup_printf( + SUBTUNE_PREFIX "%03u.sid", tnum); + return subtune; + } else + return NULL; +} + +static const char *const sidplay_suffixes[] = { + "sid", + NULL +}; + +extern const struct decoder_plugin sidplay_decoder_plugin; +const struct decoder_plugin sidplay_decoder_plugin = { + "sidplay", + sidplay_init, + sidplay_finish, + NULL, /* stream_decode() */ + sidplay_file_decode, + sidplay_tag_dup, + NULL, /* stream_tag() */ + sidplay_container_scan, + sidplay_suffixes, + NULL, /* mime_types */ +}; diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx deleted file mode 100644 index a2eb21ae4..000000000 --- a/src/decoder/sidplay_plugin.cxx +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" - -extern "C" { -#include "../decoder_api.h" -} - -#include -#include -#include - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "sidplay" - -#define SUBTUNE_PREFIX "tune_" - -static GPatternSpec *path_with_subtune; -static const char *songlength_file; -static GKeyFile *songlength_database; - -static bool all_files_are_containers; -static unsigned default_songlength; - -static bool filter_setting; - -static GKeyFile * -sidplay_load_songlength_db(const char *path) -{ - GError *error = NULL; - gchar *data; - gsize size; - - if (!g_file_get_contents(path, &data, &size, &error)) { - g_warning("unable to read songlengths file %s: %s", - path, error->message); - g_error_free(error); - return NULL; - } - - /* replace any ; comment characters with # */ - for (gsize i = 0; i < size; i++) - if (data[i] == ';') - data[i] = '#'; - - GKeyFile *db = g_key_file_new(); - bool success = g_key_file_load_from_data(db, data, size, - G_KEY_FILE_NONE, &error); - g_free(data); - if (!success) { - g_warning("unable to parse songlengths file %s: %s", - path, error->message); - g_error_free(error); - g_key_file_free(db); - return NULL; - } - - g_key_file_set_list_separator(db, ' '); - return db; -} - -static bool -sidplay_init(const struct config_param *param) -{ - /* read the songlengths database file */ - songlength_file=config_get_block_string(param, - "songlength_database", NULL); - if (songlength_file != NULL) - songlength_database = sidplay_load_songlength_db(songlength_file); - - default_songlength=config_get_block_unsigned(param, - "default_songlength", 0); - - all_files_are_containers=config_get_block_bool(param, - "all_files_are_containers", true); - - path_with_subtune=g_pattern_spec_new( - "*/" SUBTUNE_PREFIX "???.sid"); - - filter_setting=config_get_block_bool(param, "filter", true); - - return true; -} - -void -sidplay_finish() -{ - g_pattern_spec_free(path_with_subtune); - - if(songlength_database) - g_key_file_free(songlength_database); -} - -/** - * returns the file path stripped of any /tune_xxx.sid subtune - * suffix - */ -static char * -get_container_name(const char *path_fs) -{ - char *path_container=g_strdup(path_fs); - - if(!g_pattern_match(path_with_subtune, - strlen(path_container), path_container, NULL)) - return path_container; - - char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); - if(ptr) *ptr='\0'; - - return path_container; -} - -/** - * returns tune number from file.sid/tune_xxx.sid style path or 1 if - * no subtune is appended - */ -static int -get_song_num(const char *path_fs) -{ - if(g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, NULL)) { - char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); - if(!sub) return 1; - - sub+=strlen("/" SUBTUNE_PREFIX); - int song_num=strtol(sub, NULL, 10); - - if (errno == EINVAL) - return 1; - else - return song_num; - } else - return 1; -} - -/* get the song length in seconds */ -static int -get_song_length(const char *path_fs) -{ - if (songlength_database == NULL) - return -1; - - gchar *sid_file=get_container_name(path_fs); - SidTuneMod tune(sid_file); - g_free(sid_file); - if(!tune) { - g_warning("failed to load file for calculating md5 sum"); - return -1; - } - char md5sum[SIDTUNE_MD5_LENGTH+1]; - tune.createMD5(md5sum); - - int song_num=get_song_num(path_fs); - - gsize num_items; - gchar **values=g_key_file_get_string_list(songlength_database, - "Database", md5sum, &num_items, NULL); - if(!values || song_num>num_items) { - g_strfreev(values); - return -1; - } - - int minutes=strtol(values[song_num-1], NULL, 10); - if(errno==EINVAL) minutes=0; - - int seconds; - char *ptr=strchr(values[song_num-1], ':'); - if(ptr) { - seconds=strtol(ptr+1, NULL, 10); - if(errno==EINVAL) seconds=0; - } else - seconds=0; - - g_strfreev(values); - - return (minutes*60)+seconds; -} - -static void -sidplay_file_decode(struct decoder *decoder, const char *path_fs) -{ - int ret; - - /* load the tune */ - - char *path_container=get_container_name(path_fs); - SidTune tune(path_container, NULL, true); - g_free(path_container); - if (!tune) { - g_warning("failed to load file"); - return; - } - - int song_num=get_song_num(path_fs); - tune.selectSong(song_num); - - int song_len=get_song_length(path_fs); - if(song_len==-1) song_len=default_songlength; - - /* initialize the player */ - - sidplay2 player; - int iret = player.load(&tune); - if (iret != 0) { - g_warning("sidplay2.load() failed: %s", player.error()); - return; - } - - /* initialize the builder */ - - ReSIDBuilder builder("ReSID"); - if (!builder) { - g_warning("failed to initialize ReSIDBuilder"); - return; - } - - builder.create(player.info().maxsids); - if (!builder) { - g_warning("ReSIDBuilder.create() failed"); - return; - } - - builder.filter(filter_setting); - if (!builder) { - g_warning("ReSIDBuilder.filter() failed"); - return; - } - - /* configure the player */ - - sid2_config_t config = player.config(); - - config.clockDefault = SID2_CLOCK_PAL; - config.clockForced = true; - config.clockSpeed = SID2_CLOCK_CORRECT; - config.frequency = 48000; - config.optimisation = SID2_DEFAULT_OPTIMISATION; - config.playback = sid2_stereo; - config.precision = 16; - config.sidDefault = SID2_MOS6581; - config.sidEmulation = &builder; - config.sidModel = SID2_MODEL_CORRECT; - config.sidSamples = true; -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - config.sampleFormat = SID2_LITTLE_SIGNED; -#else - config.sampleFormat = SID2_BIG_SIGNED; -#endif - - iret = player.config(config); - if (iret != 0) { - g_warning("sidplay2.config() failed: %s", player.error()); - return; - } - - /* initialize the MPD decoder */ - - struct audio_format audio_format; - audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); - assert(audio_format_valid(&audio_format)); - - decoder_initialized(decoder, &audio_format, true, (float)song_len); - - /* .. and play */ - - const unsigned timebase = player.timebase(); - song_len *= timebase; - - enum decoder_command cmd; - do { - char buffer[4096]; - size_t nbytes; - - nbytes = player.play(buffer, sizeof(buffer)); - if (nbytes == 0) - break; - - decoder_timestamp(decoder, (double)player.time() / timebase); - - cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); - - if(cmd==DECODE_COMMAND_SEEK) { - unsigned data_time = player.time(); - unsigned target_time = (unsigned) - (decoder_seek_where(decoder) * timebase); - - /* can't rewind so return to zero and seek forward */ - if(target_time 0 && player.time() >= song_len) - break; - - } while (cmd != DECODE_COMMAND_STOP); -} - -static struct tag * -sidplay_tag_dup(const char *path_fs) -{ - int song_num=get_song_num(path_fs); - char *path_container=get_container_name(path_fs); - - SidTune tune(path_container, NULL, true); - g_free(path_container); - if (!tune) - return NULL; - - const SidTuneInfo &info = tune.getInfo(); - struct tag *tag = tag_new(); - - /* title */ - const char *title; - if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL) - title=info.infoString[0]; - else - title=""; - - if(info.songs>1) { - char *tag_title=g_strdup_printf("%s (%d/%d)", - title, song_num, info.songs); - tag_add_item(tag, TAG_TITLE, tag_title); - g_free(tag_title); - } else - tag_add_item(tag, TAG_TITLE, title); - - /* artist */ - if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) - tag_add_item(tag, TAG_ARTIST, info.infoString[1]); - - /* track */ - char *track=g_strdup_printf("%d", song_num); - tag_add_item(tag, TAG_TRACK, track); - g_free(track); - - /* time */ - int song_len=get_song_length(path_fs); - if(song_len!=-1) tag->time=song_len; - - return tag; -} - -static char * -sidplay_container_scan(const char *path_fs, const unsigned int tnum) -{ - SidTune tune(path_fs, NULL, true); - if (!tune) - return NULL; - - const SidTuneInfo &info=tune.getInfo(); - - /* Don't treat sids containing a single tune - as containers */ - if(!all_files_are_containers && info.songs<2) - return NULL; - - /* Construct container/tune path names, eg. - Delta.sid/tune_001.sid */ - if(tnum<=info.songs) { - char *subtune= g_strdup_printf( - SUBTUNE_PREFIX "%03u.sid", tnum); - return subtune; - } else - return NULL; -} - -static const char *const sidplay_suffixes[] = { - "sid", - NULL -}; - -extern const struct decoder_plugin sidplay_decoder_plugin; -const struct decoder_plugin sidplay_decoder_plugin = { - "sidplay", - sidplay_init, - sidplay_finish, - NULL, /* stream_decode() */ - sidplay_file_decode, - sidplay_tag_dup, - NULL, /* stream_tag() */ - sidplay_container_scan, - sidplay_suffixes, - NULL, /* mime_types */ -}; diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c new file mode 100644 index 000000000..12a6623b2 --- /dev/null +++ b/src/decoder/vorbis_decoder_plugin.c @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "_ogg_common.h" +#include "audio_check.h" +#include "uri.h" + +#ifndef HAVE_TREMOR +#define OV_EXCLUDE_STATIC_CALLBACKS +#include +#else +#include +/* Macros to make Tremor's API look like libogg. Tremor always + returns host-byte-order 16-bit signed data, and uses integer + milliseconds where libogg uses double seconds. +*/ +#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ + ov_read(VF, BUFFER, LENGTH, BITSTREAM) +#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) +#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) +#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#endif /* HAVE_TREMOR */ + +#include + +#include +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "vorbis" +#define OGG_CHUNK_SIZE 4096 + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define OGG_DECODE_USE_BIGENDIAN 1 +#else +#define OGG_DECODE_USE_BIGENDIAN 0 +#endif + +struct vorbis_decoder_data { + struct decoder *decoder; + + struct input_stream *input_stream; + bool seekable; +}; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) +{ + struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata; + size_t ret; + + ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb); + + errno = 0; + + return ret / size; +} + +static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) +{ + struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata; + + return data->seekable && + decoder_get_command(data->decoder) != DECODE_COMMAND_STOP && + input_stream_seek(data->input_stream, offset, whence, NULL) + ? 0 : -1; +} + +/* TODO: check Ogg libraries API and see if we can just not have this func */ +static int ogg_close_cb(G_GNUC_UNUSED void *vdata) +{ + return 0; +} + +static long ogg_tell_cb(void *vdata) +{ + const struct vorbis_decoder_data *data = + (const struct vorbis_decoder_data *)vdata; + + return (long)data->input_stream->offset; +} + +static const char * +vorbis_strerror(int code) +{ + switch (code) { + case OV_EREAD: + return "read error"; + + case OV_ENOTVORBIS: + return "not vorbis stream"; + + case OV_EVERSION: + return "vorbis version mismatch"; + + case OV_EBADHEADER: + return "invalid vorbis header"; + + case OV_EFAULT: + return "internal logic error"; + + default: + return "unknown error"; + } +} + +static const char * +vorbis_comment_value(const char *comment, const char *needle) +{ + size_t len = strlen(needle); + + if (g_ascii_strncasecmp(comment, needle, len) == 0 && + comment[len] == '=') + return comment + len + 1; + + return NULL; +} + +static struct replay_gain_info * +vorbis_comments_to_replay_gain(char **comments) +{ + struct replay_gain_info *rgi; + const char *temp; + bool found = false; + + rgi = replay_gain_info_new(); + + while (*comments) { + if ((temp = + vorbis_comment_value(*comments, "replaygain_track_gain"))) { + rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_gain"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_track_peak"))) { + rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_peak"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); + found = true; + } + + comments++; + } + + if (!found) { + replay_gain_info_free(rgi); + rgi = NULL; + } + + return rgi; +} + +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +vorbis_copy_comment(struct tag *tag, const char *comment, + const char *name, enum tag_type tag_type) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != NULL) { + tag_add_item(tag, tag_type, value); + return true; + } + + return false; +} + +static void +vorbis_parse_comment(struct tag *tag, const char *comment) +{ + assert(tag != NULL); + + if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, + TAG_TRACK) || + vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, + TAG_DISC) || + vorbis_copy_comment(tag, comment, "album artist", + TAG_ALBUM_ARTIST)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (vorbis_copy_comment(tag, comment, + tag_item_names[i], i)) + return; +} + +static struct tag * +vorbis_comments_to_tag(char **comments) +{ + struct tag *tag = tag_new(); + + while (*comments) + vorbis_parse_comment(tag, *comments++); + + if (tag_is_empty(tag)) { + tag_free(tag); + tag = NULL; + } + + return tag; +} + +static void +vorbis_send_comments(struct decoder *decoder, struct input_stream *is, + char **comments) +{ + struct tag *tag; + + tag = vorbis_comments_to_tag(comments); + if (!tag) + return; + + decoder_tag(decoder, is, tag); + tag_free(tag); +} + +static bool +oggvorbis_seekable(struct decoder *decoder) +{ + char *uri; + bool seekable; + + uri = decoder_get_uri(decoder); + /* disable seeking on remote streams, because libvorbis seeks + around like crazy, and due to being very expensive, this + delays song playback by 10 or 20 seconds */ + seekable = !uri_has_scheme(uri); + g_free(uri); + + return seekable; +} + +/* public */ +static void +vorbis_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + GError *error = NULL; + OggVorbis_File vf; + ov_callbacks callbacks; + struct vorbis_decoder_data data; + struct audio_format audio_format; + float total_time; + int current_section; + int prev_section = -1; + long ret; + char chunk[OGG_CHUNK_SIZE]; + long bitRate = 0; + long test; + const vorbis_info *vi; + enum decoder_command cmd = DECODE_COMMAND_NONE; + + if (ogg_stream_type_detect(input_stream) != VORBIS) + return; + + /* rewind the stream, because ogg_stream_type_detect() has + moved it */ + input_stream_seek(input_stream, 0, SEEK_SET, NULL); + + data.decoder = decoder; + data.input_stream = input_stream; + data.seekable = input_stream->seekable && oggvorbis_seekable(decoder); + + callbacks.read_func = ogg_read_cb; + callbacks.seek_func = ogg_seek_cb; + callbacks.close_func = ogg_close_cb; + callbacks.tell_func = ogg_tell_cb; + if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { + if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) + return; + + g_warning("Error decoding Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return; + } + + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); + return; + } + + if (!audio_format_init_checked(&audio_format, vi->rate, + SAMPLE_FORMAT_S16, + vi->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + + decoder_initialized(decoder, &audio_format, data.seekable, total_time); + + do { + if (cmd == DECODE_COMMAND_SEEK) { + double seek_where = decoder_seek_where(decoder); + if (0 == ov_time_seek_page(&vf, seek_where)) { + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } + + ret = ov_read(&vf, chunk, sizeof(chunk), + OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); + if (ret == OV_HOLE) /* bad packet */ + ret = 0; + else if (ret <= 0) + /* break on EOF or other error */ + break; + + if (current_section != prev_section) { + char **comments; + struct replay_gain_info *new_rgi; + + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); + break; + } + + if (vi->rate != (long)audio_format.sample_rate || + vi->channels != (int)audio_format.channels) { + /* we don't support audio format + change yet */ + g_warning("audio format change, stopping here"); + break; + } + + comments = ov_comment(&vf, -1)->user_comments; + vorbis_send_comments(decoder, input_stream, comments); + new_rgi = vorbis_comments_to_replay_gain(comments); + if (new_rgi != NULL) { + decoder_replay_gain(decoder, new_rgi); + replay_gain_info_free(new_rgi); + } + + prev_section = current_section; + } + + if ((test = ov_bitrate_instant(&vf)) > 0) + bitRate = test / 1000; + + cmd = decoder_data(decoder, input_stream, + chunk, ret, + bitRate); + } while (cmd != DECODE_COMMAND_STOP); + + ov_clear(&vf); +} + +static struct tag * +vorbis_tag_dup(const char *file) +{ + struct tag *ret; + FILE *fp; + OggVorbis_File vf; + + fp = fopen(file, "rb"); + if (!fp) { + return NULL; + } + + if (ov_open(fp, &vf, NULL, 0) < 0) { + fclose(fp); + return NULL; + } + + ret = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); + + if (!ret) + ret = tag_new(); + ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + + ov_clear(&vf); + + return ret; +} + +static const char *const vorbis_suffixes[] = { + "ogg", "oga", NULL +}; + +static const char *const vorbis_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + NULL +}; + +const struct decoder_plugin vorbis_decoder_plugin = { + .name = "vorbis", + .stream_decode = vorbis_stream_decode, + .tag_dup = vorbis_tag_dup, + .suffixes = vorbis_suffixes, + .mime_types = vorbis_mime_types +}; diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c deleted file mode 100644 index 12a6623b2..000000000 --- a/src/decoder/vorbis_plugin.c +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "_ogg_common.h" -#include "audio_check.h" -#include "uri.h" - -#ifndef HAVE_TREMOR -#define OV_EXCLUDE_STATIC_CALLBACKS -#include -#else -#include -/* Macros to make Tremor's API look like libogg. Tremor always - returns host-byte-order 16-bit signed data, and uses integer - milliseconds where libogg uses double seconds. -*/ -#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ - ov_read(VF, BUFFER, LENGTH, BITSTREAM) -#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) -#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) -#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) -#endif /* HAVE_TREMOR */ - -#include - -#include -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "vorbis" -#define OGG_CHUNK_SIZE 4096 - -#if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 -#else -#define OGG_DECODE_USE_BIGENDIAN 0 -#endif - -struct vorbis_decoder_data { - struct decoder *decoder; - - struct input_stream *input_stream; - bool seekable; -}; - -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) -{ - struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata; - size_t ret; - - ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb); - - errno = 0; - - return ret / size; -} - -static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) -{ - struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata; - - return data->seekable && - decoder_get_command(data->decoder) != DECODE_COMMAND_STOP && - input_stream_seek(data->input_stream, offset, whence, NULL) - ? 0 : -1; -} - -/* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(G_GNUC_UNUSED void *vdata) -{ - return 0; -} - -static long ogg_tell_cb(void *vdata) -{ - const struct vorbis_decoder_data *data = - (const struct vorbis_decoder_data *)vdata; - - return (long)data->input_stream->offset; -} - -static const char * -vorbis_strerror(int code) -{ - switch (code) { - case OV_EREAD: - return "read error"; - - case OV_ENOTVORBIS: - return "not vorbis stream"; - - case OV_EVERSION: - return "vorbis version mismatch"; - - case OV_EBADHEADER: - return "invalid vorbis header"; - - case OV_EFAULT: - return "internal logic error"; - - default: - return "unknown error"; - } -} - -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (g_ascii_strncasecmp(comment, needle, len) == 0 && - comment[len] == '=') - return comment + len + 1; - - return NULL; -} - -static struct replay_gain_info * -vorbis_comments_to_replay_gain(char **comments) -{ - struct replay_gain_info *rgi; - const char *temp; - bool found = false; - - rgi = replay_gain_info_new(); - - while (*comments) { - if ((temp = - vorbis_comment_value(*comments, "replaygain_track_gain"))) { - rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_gain"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_track_peak"))) { - rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_peak"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); - found = true; - } - - comments++; - } - - if (!found) { - replay_gain_info_free(rgi); - rgi = NULL; - } - - return rgi; -} - -static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; -static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -vorbis_copy_comment(struct tag *tag, const char *comment, - const char *name, enum tag_type tag_type) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != NULL) { - tag_add_item(tag, tag_type, value); - return true; - } - - return false; -} - -static void -vorbis_parse_comment(struct tag *tag, const char *comment) -{ - assert(tag != NULL); - - if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, - TAG_TRACK) || - vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, - TAG_DISC) || - vorbis_copy_comment(tag, comment, "album artist", - TAG_ALBUM_ARTIST)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (vorbis_copy_comment(tag, comment, - tag_item_names[i], i)) - return; -} - -static struct tag * -vorbis_comments_to_tag(char **comments) -{ - struct tag *tag = tag_new(); - - while (*comments) - vorbis_parse_comment(tag, *comments++); - - if (tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} - -static void -vorbis_send_comments(struct decoder *decoder, struct input_stream *is, - char **comments) -{ - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); - if (!tag) - return; - - decoder_tag(decoder, is, tag); - tag_free(tag); -} - -static bool -oggvorbis_seekable(struct decoder *decoder) -{ - char *uri; - bool seekable; - - uri = decoder_get_uri(decoder); - /* disable seeking on remote streams, because libvorbis seeks - around like crazy, and due to being very expensive, this - delays song playback by 10 or 20 seconds */ - seekable = !uri_has_scheme(uri); - g_free(uri); - - return seekable; -} - -/* public */ -static void -vorbis_stream_decode(struct decoder *decoder, - struct input_stream *input_stream) -{ - GError *error = NULL; - OggVorbis_File vf; - ov_callbacks callbacks; - struct vorbis_decoder_data data; - struct audio_format audio_format; - float total_time; - int current_section; - int prev_section = -1; - long ret; - char chunk[OGG_CHUNK_SIZE]; - long bitRate = 0; - long test; - const vorbis_info *vi; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - if (ogg_stream_type_detect(input_stream) != VORBIS) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_seek(input_stream, 0, SEEK_SET, NULL); - - data.decoder = decoder; - data.input_stream = input_stream; - data.seekable = input_stream->seekable && oggvorbis_seekable(decoder); - - callbacks.read_func = ogg_read_cb; - callbacks.seek_func = ogg_seek_cb; - callbacks.close_func = ogg_close_cb; - callbacks.tell_func = ogg_tell_cb; - if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { - if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) - return; - - g_warning("Error decoding Ogg Vorbis stream: %s", - vorbis_strerror(ret)); - return; - } - - vi = ov_info(&vf, -1); - if (vi == NULL) { - g_warning("ov_info() has failed"); - return; - } - - if (!audio_format_init_checked(&audio_format, vi->rate, - SAMPLE_FORMAT_S16, - vi->channels, &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - - decoder_initialized(decoder, &audio_format, data.seekable, total_time); - - do { - if (cmd == DECODE_COMMAND_SEEK) { - double seek_where = decoder_seek_where(decoder); - if (0 == ov_time_seek_page(&vf, seek_where)) { - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - - ret = ov_read(&vf, chunk, sizeof(chunk), - OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else if (ret <= 0) - /* break on EOF or other error */ - break; - - if (current_section != prev_section) { - char **comments; - struct replay_gain_info *new_rgi; - - vi = ov_info(&vf, -1); - if (vi == NULL) { - g_warning("ov_info() has failed"); - break; - } - - if (vi->rate != (long)audio_format.sample_rate || - vi->channels != (int)audio_format.channels) { - /* we don't support audio format - change yet */ - g_warning("audio format change, stopping here"); - break; - } - - comments = ov_comment(&vf, -1)->user_comments; - vorbis_send_comments(decoder, input_stream, comments); - new_rgi = vorbis_comments_to_replay_gain(comments); - if (new_rgi != NULL) { - decoder_replay_gain(decoder, new_rgi); - replay_gain_info_free(new_rgi); - } - - prev_section = current_section; - } - - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; - - cmd = decoder_data(decoder, input_stream, - chunk, ret, - bitRate); - } while (cmd != DECODE_COMMAND_STOP); - - ov_clear(&vf); -} - -static struct tag * -vorbis_tag_dup(const char *file) -{ - struct tag *ret; - FILE *fp; - OggVorbis_File vf; - - fp = fopen(file, "rb"); - if (!fp) { - return NULL; - } - - if (ov_open(fp, &vf, NULL, 0) < 0) { - fclose(fp); - return NULL; - } - - ret = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); - - if (!ret) - ret = tag_new(); - ret->time = (int)(ov_time_total(&vf, -1) + 0.5); - - ov_clear(&vf); - - return ret; -} - -static const char *const vorbis_suffixes[] = { - "ogg", "oga", NULL -}; - -static const char *const vorbis_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - NULL -}; - -const struct decoder_plugin vorbis_decoder_plugin = { - .name = "vorbis", - .stream_decode = vorbis_stream_decode, - .tag_dup = vorbis_tag_dup, - .suffixes = vorbis_suffixes, - .mime_types = vorbis_mime_types -}; diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/wavpack_decoder_plugin.c new file mode 100644 index 000000000..380985f85 --- /dev/null +++ b/src/decoder/wavpack_decoder_plugin.c @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" +#include "path.h" +#include "utils.h" + +#include +#include + +#include +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "wavpack" + +/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ +#define CHUNK_SIZE 1020 + +#define ERRORLEN 80 + +static struct { + const char *name; + enum tag_type type; +} tagtypes[] = { + { "artist", TAG_ARTIST }, + { "album", TAG_ALBUM }, + { "title", TAG_TITLE }, + { "track", TAG_TRACK }, + { "name", TAG_NAME }, + { "genre", TAG_GENRE }, + { "date", TAG_DATE }, + { "composer", TAG_COMPOSER }, + { "performer", TAG_PERFORMER }, + { "comment", TAG_COMMENT }, + { "disc", TAG_DISC }, +}; + +/** A pointer type for format converter function. */ +typedef void (*format_samples_t)( + int bytes_per_sample, + void *buffer, uint32_t count +); + +/* + * This function has been borrowed from the tiny player found on + * wavpack.com. Modifications were required because mpd only handles + * max 24-bit samples. + */ +static void +format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) +{ + int32_t *src = buffer; + + switch (bytes_per_sample) { + case 1: { + int8_t *dst = buffer; + /* + * The asserts like the following one are because we do the + * formatting of samples within a single buffer. The size + * of the output samples never can be greater than the size + * of the input ones. Otherwise we would have an overflow. + */ + assert_static(sizeof(*dst) <= sizeof(*src)); + + /* pass through and align 8-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + case 2: { + uint16_t *dst = buffer; + assert_static(sizeof(*dst) <= sizeof(*src)); + + /* pass through and align 16-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + + case 3: + case 4: + /* do nothing */ + break; + } +} + +/* + * This function converts floating point sample data to 24-bit integer. + */ +static void +format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, + uint32_t count) +{ + int32_t *dst = buffer; + float *src = buffer; + assert_static(sizeof(*dst) <= sizeof(*src)); + + while (count--) { + *dst++ = (int32_t)(*src++ + 0.5f); + } +} + +/** + * Choose a MPD sample format from libwavpacks' number of bits. + */ +static enum sample_format +wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) +{ + if (is_float) + return SAMPLE_FORMAT_S24_P32; + + switch (bytes_per_sample) { + case 1: + return SAMPLE_FORMAT_S8; + + case 2: + return SAMPLE_FORMAT_S16; + + case 3: + return SAMPLE_FORMAT_S24_P32; + + case 4: + return SAMPLE_FORMAT_S32; + + default: + return SAMPLE_FORMAT_UNDEFINED; + } +} + +/* + * This does the main decoding thing. + * Requires an already opened WavpackContext. + */ +static void +wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek) +{ + GError *error = NULL; + bool is_float; + enum sample_format sample_format; + struct audio_format audio_format; + format_samples_t format_samples; + char chunk[CHUNK_SIZE]; + int samples_requested, samples_got; + float total_time; + int bytes_per_sample, output_sample_size; + + is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + if (!audio_format_init_checked(&audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), &error)) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) { + format_samples = format_samples_float; + } else { + format_samples = format_samples_int; + } + + total_time = WavpackGetNumSamples(wpc); + total_time /= audio_format.sample_rate; + bytes_per_sample = WavpackGetBytesPerSample(wpc); + output_sample_size = audio_format_frame_size(&audio_format); + + /* wavpack gives us all kind of samples in a 32-bit space */ + samples_requested = sizeof(chunk) / (4 * audio_format.channels); + + decoder_initialized(decoder, &audio_format, can_seek, total_time); + + do { + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + if (can_seek) { + unsigned where = decoder_seek_where(decoder) * + audio_format.sample_rate; + + if (WavpackSeekSample(wpc, where)) { + decoder_command_finished(decoder); + } else { + decoder_seek_error(decoder); + } + } else { + decoder_seek_error(decoder); + } + } + + if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) { + break; + } + + samples_got = WavpackUnpackSamples( + wpc, (int32_t *)chunk, samples_requested + ); + if (samples_got > 0) { + int bitrate = (int)(WavpackGetInstantBitrate(wpc) / + 1000 + 0.5); + + format_samples( + bytes_per_sample, chunk, + samples_got * audio_format.channels + ); + + decoder_data( + decoder, NULL, chunk, + samples_got * output_sample_size, + bitrate + ); + } + } while (samples_got > 0); +} + +/** + * Locate and parse a floating point tag. Returns true if it was + * found. + */ +static bool +wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) +{ + char buffer[64]; + int ret; + + ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)); + if (ret <= 0) + return false; + + *value_r = atof(buffer); + return true; +} + +static struct replay_gain_info * +wavpack_replaygain(WavpackContext *wpc) +{ + struct replay_gain_info *replay_gain_info; + bool found = false; + + replay_gain_info = replay_gain_info_new(); + + found |= wavpack_tag_float( + wpc, "replaygain_track_gain", + &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain + ); + found |= wavpack_tag_float( + wpc, "replaygain_track_peak", + &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak + ); + found |= wavpack_tag_float( + wpc, "replaygain_album_gain", + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain + ); + found |= wavpack_tag_float( + wpc, "replaygain_album_peak", + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak + ); + + if (found) { + return replay_gain_info; + } + + replay_gain_info_free(replay_gain_info); + + return NULL; +} + +/* + * Reads metainfo from the specified file. + */ +static struct tag * +wavpack_tagdup(const char *fname) +{ + WavpackContext *wpc; + struct tag *tag; + char error[ERRORLEN]; + char *s; + int size, allocated_size; + + wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); + if (wpc == NULL) { + g_warning( + "failed to open WavPack file \"%s\": %s\n", + fname, error + ); + return NULL; + } + + tag = tag_new(); + tag->time = WavpackGetNumSamples(wpc); + tag->time /= WavpackGetSampleRate(wpc); + + allocated_size = 0; + s = NULL; + + for (unsigned i = 0; i < G_N_ELEMENTS(tagtypes); ++i) { + size = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0); + if (size > 0) { + ++size; /* EOS */ + + if (s == NULL) { + s = g_malloc(size); + allocated_size = size; + } else if (size > allocated_size) { + char *t = (char *)g_realloc(s, size); + allocated_size = size; + s = t; + } + + WavpackGetTagItem(wpc, tagtypes[i].name, s, size); + tag_add_item(tag, tagtypes[i].type, s); + } + } + + g_free(s); + + WavpackCloseFile(wpc); + + return tag; +} + +/* + * mpd input_stream <=> WavpackStreamReader wrapper callbacks + */ + +/* This struct is needed for per-stream last_byte storage. */ +struct wavpack_input { + struct decoder *decoder; + struct input_stream *is; + /* Needed for push_back_byte() */ + int last_byte; +}; + +/** + * Little wrapper for struct wavpack_input to cast from void *. + */ +static struct wavpack_input * +wpin(void *id) +{ + assert(id); + return id; +} + +static int32_t +wavpack_input_read_bytes(void *id, void *data, int32_t bcount) +{ + uint8_t *buf = (uint8_t *)data; + int32_t i = 0; + + if (wpin(id)->last_byte != EOF) { + *buf++ = wpin(id)->last_byte; + wpin(id)->last_byte = EOF; + --bcount; + ++i; + } + + /* wavpack fails if we return a partial read, so we just wait + until the buffer is full */ + while (bcount > 0) { + size_t nbytes = decoder_read( + wpin(id)->decoder, wpin(id)->is, buf, bcount + ); + if (nbytes == 0) { + /* EOF, error or a decoder command */ + break; + } + + i += nbytes; + bcount -= nbytes; + buf += nbytes; + } + + return i; +} + +static uint32_t +wavpack_input_get_pos(void *id) +{ + return wpin(id)->is->offset; +} + +static int +wavpack_input_set_pos_abs(void *id, uint32_t pos) +{ + return input_stream_seek(wpin(id)->is, pos, SEEK_SET, NULL) ? 0 : -1; +} + +static int +wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) +{ + return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1; +} + +static int +wavpack_input_push_back_byte(void *id, int c) +{ + if (wpin(id)->last_byte == EOF) { + wpin(id)->last_byte = c; + return c; + } else { + return EOF; + } +} + +static uint32_t +wavpack_input_get_length(void *id) +{ + if (wpin(id)->is->size < 0) + return 0; + + return wpin(id)->is->size; +} + +static int +wavpack_input_can_seek(void *id) +{ + return wpin(id)->is->seekable; +} + +static WavpackStreamReader mpd_is_reader = { + .read_bytes = wavpack_input_read_bytes, + .get_pos = wavpack_input_get_pos, + .set_pos_abs = wavpack_input_set_pos_abs, + .set_pos_rel = wavpack_input_set_pos_rel, + .push_back_byte = wavpack_input_push_back_byte, + .get_length = wavpack_input_get_length, + .can_seek = wavpack_input_can_seek, + .write_bytes = NULL /* no need to write edited tags */ +}; + +static void +wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, + struct input_stream *is) +{ + isp->decoder = decoder; + isp->is = is; + isp->last_byte = EOF; +} + +static struct input_stream * +wavpack_open_wvc(struct decoder *decoder, struct wavpack_input *wpi) +{ + struct input_stream *is_wvc; + char *utf8url; + char *wvc_url = NULL; + char first_byte; + size_t nbytes; + + /* + * As we use dc->utf8url, this function will be bad for + * single files. utf8url is not absolute file path :/ + */ + utf8url = decoder_get_uri(decoder); + if (utf8url == NULL) { + return false; + } + + wvc_url = g_strconcat(utf8url, "c", NULL); + g_free(utf8url); + + is_wvc = input_stream_open(wvc_url, NULL); + g_free(wvc_url); + + if (is_wvc == NULL) + return NULL; + + /* + * And we try to buffer in order to get know + * about a possible 404 error. + */ + nbytes = decoder_read( + decoder, is_wvc, &first_byte, sizeof(first_byte) + ); + if (nbytes == 0) { + input_stream_close(is_wvc); + return NULL; + } + + /* push it back */ + wavpack_input_init(wpi, decoder, is_wvc); + wpi->last_byte = first_byte; + return is_wvc; +} + +/* + * Decodes a stream. + */ +static void +wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + struct input_stream *is_wvc; + int open_flags = OPEN_NORMALIZE; + struct wavpack_input isp, isp_wvc; + bool can_seek = is->seekable; + + is_wvc = wavpack_open_wvc(decoder, &isp_wvc); + if (is_wvc != NULL) { + open_flags |= OPEN_WVC; + can_seek &= is_wvc->seekable; + } + + if (!can_seek) { + open_flags |= OPEN_STREAMING; + } + + wavpack_input_init(&isp, decoder, is); + wpc = WavpackOpenFileInputEx( + &mpd_is_reader, &isp, + open_flags & OPEN_WVC ? &isp_wvc : NULL, + error, open_flags, 23 + ); + + if (wpc == NULL) { + g_warning("failed to open WavPack stream: %s\n", error); + return; + } + + wavpack_decode(decoder, wpc, can_seek); + + WavpackCloseFile(wpc); + if (open_flags & OPEN_WVC) { + input_stream_close(is_wvc); + } +} + +/* + * Decodes a file. + */ +static void +wavpack_filedecode(struct decoder *decoder, const char *fname) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + struct replay_gain_info *replay_gain_info; + + wpc = WavpackOpenFileInput( + fname, error, + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 + ); + if (wpc == NULL) { + g_warning( + "failed to open WavPack file \"%s\": %s\n", + fname, error + ); + return; + } + + replay_gain_info = wavpack_replaygain(wpc); + if (replay_gain_info != NULL) { + decoder_replay_gain(decoder, replay_gain_info); + replay_gain_info_free(replay_gain_info); + } + + wavpack_decode(decoder, wpc, true); + + WavpackCloseFile(wpc); +} + +static char const *const wavpack_suffixes[] = { + "wv", + NULL +}; + +static char const *const wavpack_mime_types[] = { + "audio/x-wavpack", + NULL +}; + +const struct decoder_plugin wavpack_decoder_plugin = { + .name = "wavpack", + .stream_decode = wavpack_streamdecode, + .file_decode = wavpack_filedecode, + .tag_dup = wavpack_tagdup, + .suffixes = wavpack_suffixes, + .mime_types = wavpack_mime_types +}; diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c deleted file mode 100644 index 380985f85..000000000 --- a/src/decoder/wavpack_plugin.c +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" -#include "audio_check.h" -#include "path.h" -#include "utils.h" - -#include -#include - -#include -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "wavpack" - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -#define ERRORLEN 80 - -static struct { - const char *name; - enum tag_type type; -} tagtypes[] = { - { "artist", TAG_ARTIST }, - { "album", TAG_ALBUM }, - { "title", TAG_TITLE }, - { "track", TAG_TRACK }, - { "name", TAG_NAME }, - { "genre", TAG_GENRE }, - { "date", TAG_DATE }, - { "composer", TAG_COMPOSER }, - { "performer", TAG_PERFORMER }, - { "comment", TAG_COMMENT }, - { "disc", TAG_DISC }, -}; - -/** A pointer type for format converter function. */ -typedef void (*format_samples_t)( - int bytes_per_sample, - void *buffer, uint32_t count -); - -/* - * This function has been borrowed from the tiny player found on - * wavpack.com. Modifications were required because mpd only handles - * max 24-bit samples. - */ -static void -format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) -{ - int32_t *src = buffer; - - switch (bytes_per_sample) { - case 1: { - int8_t *dst = buffer; - /* - * The asserts like the following one are because we do the - * formatting of samples within a single buffer. The size - * of the output samples never can be greater than the size - * of the input ones. Otherwise we would have an overflow. - */ - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* pass through and align 8-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - case 2: { - uint16_t *dst = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* pass through and align 16-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - - case 3: - case 4: - /* do nothing */ - break; - } -} - -/* - * This function converts floating point sample data to 24-bit integer. - */ -static void -format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, - uint32_t count) -{ - int32_t *dst = buffer; - float *src = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); - - while (count--) { - *dst++ = (int32_t)(*src++ + 0.5f); - } -} - -/** - * Choose a MPD sample format from libwavpacks' number of bits. - */ -static enum sample_format -wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) -{ - if (is_float) - return SAMPLE_FORMAT_S24_P32; - - switch (bytes_per_sample) { - case 1: - return SAMPLE_FORMAT_S8; - - case 2: - return SAMPLE_FORMAT_S16; - - case 3: - return SAMPLE_FORMAT_S24_P32; - - case 4: - return SAMPLE_FORMAT_S32; - - default: - return SAMPLE_FORMAT_UNDEFINED; - } -} - -/* - * This does the main decoding thing. - * Requires an already opened WavpackContext. - */ -static void -wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek) -{ - GError *error = NULL; - bool is_float; - enum sample_format sample_format; - struct audio_format audio_format; - format_samples_t format_samples; - char chunk[CHUNK_SIZE]; - int samples_requested, samples_got; - float total_time; - int bytes_per_sample, output_sample_size; - - is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; - sample_format = - wavpack_bits_to_sample_format(is_float, - WavpackGetBytesPerSample(wpc)); - - if (!audio_format_init_checked(&audio_format, - WavpackGetSampleRate(wpc), - sample_format, - WavpackGetNumChannels(wpc), &error)) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) { - format_samples = format_samples_float; - } else { - format_samples = format_samples_int; - } - - total_time = WavpackGetNumSamples(wpc); - total_time /= audio_format.sample_rate; - bytes_per_sample = WavpackGetBytesPerSample(wpc); - output_sample_size = audio_format_frame_size(&audio_format); - - /* wavpack gives us all kind of samples in a 32-bit space */ - samples_requested = sizeof(chunk) / (4 * audio_format.channels); - - decoder_initialized(decoder, &audio_format, can_seek, total_time); - - do { - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { - if (can_seek) { - unsigned where = decoder_seek_where(decoder) * - audio_format.sample_rate; - - if (WavpackSeekSample(wpc, where)) { - decoder_command_finished(decoder); - } else { - decoder_seek_error(decoder); - } - } else { - decoder_seek_error(decoder); - } - } - - if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) { - break; - } - - samples_got = WavpackUnpackSamples( - wpc, (int32_t *)chunk, samples_requested - ); - if (samples_got > 0) { - int bitrate = (int)(WavpackGetInstantBitrate(wpc) / - 1000 + 0.5); - - format_samples( - bytes_per_sample, chunk, - samples_got * audio_format.channels - ); - - decoder_data( - decoder, NULL, chunk, - samples_got * output_sample_size, - bitrate - ); - } - } while (samples_got > 0); -} - -/** - * Locate and parse a floating point tag. Returns true if it was - * found. - */ -static bool -wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) -{ - char buffer[64]; - int ret; - - ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)); - if (ret <= 0) - return false; - - *value_r = atof(buffer); - return true; -} - -static struct replay_gain_info * -wavpack_replaygain(WavpackContext *wpc) -{ - struct replay_gain_info *replay_gain_info; - bool found = false; - - replay_gain_info = replay_gain_info_new(); - - found |= wavpack_tag_float( - wpc, "replaygain_track_gain", - &replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain - ); - found |= wavpack_tag_float( - wpc, "replaygain_track_peak", - &replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak - ); - found |= wavpack_tag_float( - wpc, "replaygain_album_gain", - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain - ); - found |= wavpack_tag_float( - wpc, "replaygain_album_peak", - &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak - ); - - if (found) { - return replay_gain_info; - } - - replay_gain_info_free(replay_gain_info); - - return NULL; -} - -/* - * Reads metainfo from the specified file. - */ -static struct tag * -wavpack_tagdup(const char *fname) -{ - WavpackContext *wpc; - struct tag *tag; - char error[ERRORLEN]; - char *s; - int size, allocated_size; - - wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); - if (wpc == NULL) { - g_warning( - "failed to open WavPack file \"%s\": %s\n", - fname, error - ); - return NULL; - } - - tag = tag_new(); - tag->time = WavpackGetNumSamples(wpc); - tag->time /= WavpackGetSampleRate(wpc); - - allocated_size = 0; - s = NULL; - - for (unsigned i = 0; i < G_N_ELEMENTS(tagtypes); ++i) { - size = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0); - if (size > 0) { - ++size; /* EOS */ - - if (s == NULL) { - s = g_malloc(size); - allocated_size = size; - } else if (size > allocated_size) { - char *t = (char *)g_realloc(s, size); - allocated_size = size; - s = t; - } - - WavpackGetTagItem(wpc, tagtypes[i].name, s, size); - tag_add_item(tag, tagtypes[i].type, s); - } - } - - g_free(s); - - WavpackCloseFile(wpc); - - return tag; -} - -/* - * mpd input_stream <=> WavpackStreamReader wrapper callbacks - */ - -/* This struct is needed for per-stream last_byte storage. */ -struct wavpack_input { - struct decoder *decoder; - struct input_stream *is; - /* Needed for push_back_byte() */ - int last_byte; -}; - -/** - * Little wrapper for struct wavpack_input to cast from void *. - */ -static struct wavpack_input * -wpin(void *id) -{ - assert(id); - return id; -} - -static int32_t -wavpack_input_read_bytes(void *id, void *data, int32_t bcount) -{ - uint8_t *buf = (uint8_t *)data; - int32_t i = 0; - - if (wpin(id)->last_byte != EOF) { - *buf++ = wpin(id)->last_byte; - wpin(id)->last_byte = EOF; - --bcount; - ++i; - } - - /* wavpack fails if we return a partial read, so we just wait - until the buffer is full */ - while (bcount > 0) { - size_t nbytes = decoder_read( - wpin(id)->decoder, wpin(id)->is, buf, bcount - ); - if (nbytes == 0) { - /* EOF, error or a decoder command */ - break; - } - - i += nbytes; - bcount -= nbytes; - buf += nbytes; - } - - return i; -} - -static uint32_t -wavpack_input_get_pos(void *id) -{ - return wpin(id)->is->offset; -} - -static int -wavpack_input_set_pos_abs(void *id, uint32_t pos) -{ - return input_stream_seek(wpin(id)->is, pos, SEEK_SET, NULL) ? 0 : -1; -} - -static int -wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) -{ - return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1; -} - -static int -wavpack_input_push_back_byte(void *id, int c) -{ - if (wpin(id)->last_byte == EOF) { - wpin(id)->last_byte = c; - return c; - } else { - return EOF; - } -} - -static uint32_t -wavpack_input_get_length(void *id) -{ - if (wpin(id)->is->size < 0) - return 0; - - return wpin(id)->is->size; -} - -static int -wavpack_input_can_seek(void *id) -{ - return wpin(id)->is->seekable; -} - -static WavpackStreamReader mpd_is_reader = { - .read_bytes = wavpack_input_read_bytes, - .get_pos = wavpack_input_get_pos, - .set_pos_abs = wavpack_input_set_pos_abs, - .set_pos_rel = wavpack_input_set_pos_rel, - .push_back_byte = wavpack_input_push_back_byte, - .get_length = wavpack_input_get_length, - .can_seek = wavpack_input_can_seek, - .write_bytes = NULL /* no need to write edited tags */ -}; - -static void -wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, - struct input_stream *is) -{ - isp->decoder = decoder; - isp->is = is; - isp->last_byte = EOF; -} - -static struct input_stream * -wavpack_open_wvc(struct decoder *decoder, struct wavpack_input *wpi) -{ - struct input_stream *is_wvc; - char *utf8url; - char *wvc_url = NULL; - char first_byte; - size_t nbytes; - - /* - * As we use dc->utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - utf8url = decoder_get_uri(decoder); - if (utf8url == NULL) { - return false; - } - - wvc_url = g_strconcat(utf8url, "c", NULL); - g_free(utf8url); - - is_wvc = input_stream_open(wvc_url, NULL); - g_free(wvc_url); - - if (is_wvc == NULL) - return NULL; - - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - nbytes = decoder_read( - decoder, is_wvc, &first_byte, sizeof(first_byte) - ); - if (nbytes == 0) { - input_stream_close(is_wvc); - return NULL; - } - - /* push it back */ - wavpack_input_init(wpi, decoder, is_wvc); - wpi->last_byte = first_byte; - return is_wvc; -} - -/* - * Decodes a stream. - */ -static void -wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - struct input_stream *is_wvc; - int open_flags = OPEN_NORMALIZE; - struct wavpack_input isp, isp_wvc; - bool can_seek = is->seekable; - - is_wvc = wavpack_open_wvc(decoder, &isp_wvc); - if (is_wvc != NULL) { - open_flags |= OPEN_WVC; - can_seek &= is_wvc->seekable; - } - - if (!can_seek) { - open_flags |= OPEN_STREAMING; - } - - wavpack_input_init(&isp, decoder, is); - wpc = WavpackOpenFileInputEx( - &mpd_is_reader, &isp, - open_flags & OPEN_WVC ? &isp_wvc : NULL, - error, open_flags, 23 - ); - - if (wpc == NULL) { - g_warning("failed to open WavPack stream: %s\n", error); - return; - } - - wavpack_decode(decoder, wpc, can_seek); - - WavpackCloseFile(wpc); - if (open_flags & OPEN_WVC) { - input_stream_close(is_wvc); - } -} - -/* - * Decodes a file. - */ -static void -wavpack_filedecode(struct decoder *decoder, const char *fname) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - struct replay_gain_info *replay_gain_info; - - wpc = WavpackOpenFileInput( - fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 - ); - if (wpc == NULL) { - g_warning( - "failed to open WavPack file \"%s\": %s\n", - fname, error - ); - return; - } - - replay_gain_info = wavpack_replaygain(wpc); - if (replay_gain_info != NULL) { - decoder_replay_gain(decoder, replay_gain_info); - replay_gain_info_free(replay_gain_info); - } - - wavpack_decode(decoder, wpc, true); - - WavpackCloseFile(wpc); -} - -static char const *const wavpack_suffixes[] = { - "wv", - NULL -}; - -static char const *const wavpack_mime_types[] = { - "audio/x-wavpack", - NULL -}; - -const struct decoder_plugin wavpack_decoder_plugin = { - .name = "wavpack", - .stream_decode = wavpack_streamdecode, - .file_decode = wavpack_filedecode, - .tag_dup = wavpack_tagdup, - .suffixes = wavpack_suffixes, - .mime_types = wavpack_mime_types -}; diff --git a/src/decoder/wildmidi_decoder_plugin.c b/src/decoder/wildmidi_decoder_plugin.c new file mode 100644 index 000000000..60696fb1a --- /dev/null +++ b/src/decoder/wildmidi_decoder_plugin.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" + +#include + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "wildmidi" + +enum { + WILDMIDI_SAMPLE_RATE = 48000, +}; + +static bool +wildmidi_init(const struct config_param *param) +{ + const char *config_file; + int ret; + + config_file = config_get_block_string(param, "config_file", + "/etc/timidity/timidity.cfg"); + if (!g_file_test(config_file, G_FILE_TEST_IS_REGULAR)) { + g_debug("configuration file does not exist: %s", config_file); + return false; + } + + ret = WildMidi_Init(config_file, WILDMIDI_SAMPLE_RATE, 0); + return ret == 0; +} + +static void +wildmidi_finish(void) +{ + WildMidi_Shutdown(); +} + +static void +wildmidi_file_decode(struct decoder *decoder, const char *path_fs) +{ + static const struct audio_format audio_format = { + .sample_rate = WILDMIDI_SAMPLE_RATE, + .format = SAMPLE_FORMAT_S16, + .channels = 2, + }; + midi *wm; + const struct _WM_Info *info; + enum decoder_command cmd; + + wm = WildMidi_Open(path_fs); + if (wm == NULL) + return; + + info = WildMidi_GetInfo(wm); + if (info == NULL) { + WildMidi_Close(wm); + return; + } + + decoder_initialized(decoder, &audio_format, true, + info->approx_total_samples / WILDMIDI_SAMPLE_RATE); + + do { + char buffer[4096]; + int len; + + info = WildMidi_GetInfo(wm); + if (info == NULL) + break; + + len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); + if (len <= 0) + break; + + cmd = decoder_data(decoder, NULL, buffer, len, 0); + + if (cmd == DECODE_COMMAND_SEEK) { + unsigned long seek_where = WILDMIDI_SAMPLE_RATE * + decoder_seek_where(decoder); + + WildMidi_SampledSeek(wm, &seek_where); + decoder_command_finished(decoder); + cmd = DECODE_COMMAND_NONE; + } + + } while (cmd == DECODE_COMMAND_NONE); + + WildMidi_Close(wm); +} + +static struct tag * +wildmidi_tag_dup(const char *path_fs) +{ + midi *wm; + const struct _WM_Info *info; + struct tag *tag; + + wm = WildMidi_Open(path_fs); + if (wm == NULL) + return NULL; + + info = WildMidi_GetInfo(wm); + if (info == NULL) { + WildMidi_Close(wm); + return NULL; + } + + tag = tag_new(); + tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; + + WildMidi_Close(wm); + + return tag; +} + +static const char *const wildmidi_suffixes[] = { + "mid", + NULL +}; + +const struct decoder_plugin wildmidi_decoder_plugin = { + .name = "wildmidi", + .init = wildmidi_init, + .finish = wildmidi_finish, + .file_decode = wildmidi_file_decode, + .tag_dup = wildmidi_tag_dup, + .suffixes = wildmidi_suffixes, +}; diff --git a/src/decoder/wildmidi_plugin.c b/src/decoder/wildmidi_plugin.c deleted file mode 100644 index 60696fb1a..000000000 --- a/src/decoder/wildmidi_plugin.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "decoder_api.h" - -#include - -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "wildmidi" - -enum { - WILDMIDI_SAMPLE_RATE = 48000, -}; - -static bool -wildmidi_init(const struct config_param *param) -{ - const char *config_file; - int ret; - - config_file = config_get_block_string(param, "config_file", - "/etc/timidity/timidity.cfg"); - if (!g_file_test(config_file, G_FILE_TEST_IS_REGULAR)) { - g_debug("configuration file does not exist: %s", config_file); - return false; - } - - ret = WildMidi_Init(config_file, WILDMIDI_SAMPLE_RATE, 0); - return ret == 0; -} - -static void -wildmidi_finish(void) -{ - WildMidi_Shutdown(); -} - -static void -wildmidi_file_decode(struct decoder *decoder, const char *path_fs) -{ - static const struct audio_format audio_format = { - .sample_rate = WILDMIDI_SAMPLE_RATE, - .format = SAMPLE_FORMAT_S16, - .channels = 2, - }; - midi *wm; - const struct _WM_Info *info; - enum decoder_command cmd; - - wm = WildMidi_Open(path_fs); - if (wm == NULL) - return; - - info = WildMidi_GetInfo(wm); - if (info == NULL) { - WildMidi_Close(wm); - return; - } - - decoder_initialized(decoder, &audio_format, true, - info->approx_total_samples / WILDMIDI_SAMPLE_RATE); - - do { - char buffer[4096]; - int len; - - info = WildMidi_GetInfo(wm); - if (info == NULL) - break; - - len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); - if (len <= 0) - break; - - cmd = decoder_data(decoder, NULL, buffer, len, 0); - - if (cmd == DECODE_COMMAND_SEEK) { - unsigned long seek_where = WILDMIDI_SAMPLE_RATE * - decoder_seek_where(decoder); - - WildMidi_SampledSeek(wm, &seek_where); - decoder_command_finished(decoder); - cmd = DECODE_COMMAND_NONE; - } - - } while (cmd == DECODE_COMMAND_NONE); - - WildMidi_Close(wm); -} - -static struct tag * -wildmidi_tag_dup(const char *path_fs) -{ - midi *wm; - const struct _WM_Info *info; - struct tag *tag; - - wm = WildMidi_Open(path_fs); - if (wm == NULL) - return NULL; - - info = WildMidi_GetInfo(wm); - if (info == NULL) { - WildMidi_Close(wm); - return NULL; - } - - tag = tag_new(); - tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; - - WildMidi_Close(wm); - - return tag; -} - -static const char *const wildmidi_suffixes[] = { - "mid", - NULL -}; - -const struct decoder_plugin wildmidi_decoder_plugin = { - .name = "wildmidi", - .init = wildmidi_init, - .finish = wildmidi_finish, - .file_decode = wildmidi_file_decode, - .tag_dup = wildmidi_tag_dup, - .suffixes = wildmidi_suffixes, -}; -- cgit v1.2.3