diff options
Diffstat (limited to 'src/decoder')
28 files changed, 1223 insertions, 320 deletions
diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx new file mode 100644 index 000000000..b3b7f1d73 --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.cxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2012 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 "AdPlugDecoderPlugin.h" +#include "tag_handler.h" + +extern "C" { +#include "decoder_api.h" +#include "audio_check.h" +} + +#include <adplug/adplug.h> +#include <adplug/emuopl.h> + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "adplug" + +static unsigned sample_rate; + +static bool +adplug_init(const struct config_param *param) +{ + GError *error = NULL; + + sample_rate = config_get_block_unsigned(param, "sample_rate", 48000); + if (!audio_check_sample_rate(sample_rate, &error)) { + g_warning("%s\n", error->message); + g_error_free(error); + return false; + } + + return true; +} + +static void +adplug_file_decode(struct decoder *decoder, const char *path_fs) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return; + + struct audio_format audio_format; + audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, false, + player->songlength() / 1000.); + + int16_t buffer[2048]; + const unsigned frames_per_buffer = G_N_ELEMENTS(buffer) / 2; + enum decoder_command cmd; + + do { + if (!player->update()) + break; + + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, NULL, + buffer, sizeof(buffer), + 0); + } while (cmd == DECODE_COMMAND_NONE); + + delete player; +} + +static void +adplug_scan_tag(enum tag_type type, const std::string &value, + const struct tag_handler *handler, void *handler_ctx) +{ + if (!value.empty()) + tag_handler_invoke_tag(handler, handler_ctx, + type, value.c_str()); +} + +static bool +adplug_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs, &opl); + if (player == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + player->songlength() / 1000); + + if (handler->tag != nullptr) { + adplug_scan_tag(TAG_TITLE, player->gettitle(), + handler, handler_ctx); + adplug_scan_tag(TAG_ARTIST, player->getauthor(), + handler, handler_ctx); + adplug_scan_tag(TAG_COMMENT, player->getdesc(), + handler, handler_ctx); + } + + delete player; + return true; +} + +static const char *const adplug_suffixes[] = { + "amd", + "d00", + "hsc", + "laa", + "rad", + "raw", + "sa2", + nullptr +}; + +const struct decoder_plugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/AdPlugDecoderPlugin.h new file mode 100644 index 000000000..9fdf438aa --- /dev/null +++ b/src/decoder/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_DECODER_ADPLUG_H +#define MPD_DECODER_ADPLUG_H + +extern const struct decoder_plugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/_flac_common.c b/src/decoder/FLACCommon.cxx index d7f0c4a8a..8e86b6ae4 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/FLACCommon.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,13 @@ */ #include "config.h" -#include "_flac_common.h" -#include "flac_metadata.h" -#include "flac_pcm.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" +#include "FLAC_PCM.hxx" + +extern "C" { #include "audio_check.h" +} #include <glib.h> @@ -46,7 +49,7 @@ flac_data_init(struct flac_data *data, struct decoder * decoder, data->position = 0; data->decoder = decoder; data->input_stream = input_stream; - data->tag = NULL; + data->tag = nullptr; } void @@ -54,7 +57,7 @@ flac_data_deinit(struct flac_data *data) { pcm_buffer_deinit(&data->buffer); - if (data->tag != NULL) + if (data->tag != nullptr) tag_free(data->tag); } @@ -86,7 +89,7 @@ flac_got_stream_info(struct flac_data *data, if (data->initialized || data->unsupported) return; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, stream_info->sample_rate, flac_sample_format(stream_info->bits_per_sample), @@ -129,8 +132,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, decoder_mixramp(data->decoder, replay_gain_db, mixramp_start, mixramp_end); - if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, + if (data->tag != nullptr) + flac_vorbis_comments_to_tag(data->tag, nullptr, &block->data.vorbis_comment); default: @@ -160,7 +163,7 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) if (data->unsupported) return false; - GError *error = NULL; + GError *error = nullptr; if (!audio_format_init_checked(&data->audio_format, header->sample_rate, flac_sample_format(header->bits_per_sample), @@ -199,7 +202,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, buffer = pcm_buffer_get(&data->buffer, buffer_size); flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, + (enum sample_format)data->audio_format.format, buf, 0, frame->header.blocksize); if (nbytes > 0) diff --git a/src/decoder/_flac_common.h b/src/decoder/FLACCommon.hxx index 0d90ba656..3d280cc49 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/FLACCommon.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,13 @@ * Common data structures and functions used by FLAC and OggFLAC */ -#ifndef MPD_FLAC_COMMON_H -#define MPD_FLAC_COMMON_H +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX +extern "C" { #include "decoder_api.h" #include "pcm_buffer.h" - -#include <glib.h> +} #include <FLAC/stream_decoder.h> #include <FLAC/metadata.h> diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/FLACDecoderPlugin.cxx index fb0b3502d..0b543ea65 100644 --- a/src/decoder/flac_decoder_plugin.c +++ b/src/decoder/FLACDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,13 +18,13 @@ */ #include "config.h" /* must be first for large file support */ -#include "_flac_common.h" -#include "flac_compat.h" -#include "flac_metadata.h" +#include "FLACDecoderPlugin.h" +#include "FLACCommon.hxx" +#include "FLACMetaData.hxx" -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -#include "_ogg_common.h" -#endif +extern "C" { +#include "ogg_codec.h" +} #include <glib.h> @@ -34,14 +34,18 @@ #include <sys/stat.h> #include <sys/types.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#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, + FLAC__byte buf[], size_t *bytes, void *fdata) { - struct flac_data *data = fdata; + struct flac_data *data = (struct flac_data *)fdata; size_t r; r = decoder_read(data->decoder, data->input_stream, @@ -69,7 +73,7 @@ flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, - NULL)) + nullptr)) return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; return FLAC__STREAM_DECODER_SEEK_STATUS_OK; @@ -120,28 +124,6 @@ flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, flac_error_common_cb(status, (struct flac_data *) fdata); } -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - 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: - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - break; - } - - g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacPrintErroredState(FLAC__StreamDecoderState state) { switch (state) { @@ -162,7 +144,6 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state) g_warning("%s\n", FLAC__StreamDecoderStateString[state]); } -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, const FLAC__StreamMetadata * block, void *vdata) @@ -195,7 +176,7 @@ static bool flac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { - return flac_scan_file2(file, NULL, handler, handler_ctx); + return flac_scan_file2(file, nullptr, handler, handler_ctx); } /** @@ -205,15 +186,13 @@ static FLAC__StreamDecoder * flac_decoder_new(void) { FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == NULL) { + if (sd == nullptr) { g_warning("FLAC__stream_decoder_new() failed"); - return NULL; + return nullptr; } -#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; } @@ -259,7 +238,7 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, data->first_frame = t_start; while (true) { - if (data->tag != NULL && !tag_is_empty(data->tag)) { + if (data->tag != nullptr && !tag_is_empty(data->tag)) { cmd = decoder_tag(data->decoder, data->input_stream, data->tag); tag_free(data->tag); @@ -300,7 +279,6 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, static FLAC__StreamDecoderInitStatus stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) { -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 return FLAC__stream_decoder_init_ogg_stream(flac_dec, flac_read_cb, flac_seek_cb, @@ -311,12 +289,6 @@ stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) flacMetadata, flac_error_cb, data); -#else - (void)flac_dec; - (void)data; - - return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -#endif } static FLAC__StreamDecoderInitStatus @@ -348,7 +320,7 @@ flac_decode_internal(struct decoder * decoder, struct flac_data data; flac_dec = flac_decoder_new(); - if (flac_dec == NULL) + if (flac_dec == nullptr) return; flac_data_init(&data, decoder, input_stream); @@ -359,9 +331,7 @@ flac_decode_internal(struct decoder * decoder, if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { flac_data_deinit(&data); FLAC__stream_decoder_delete(flac_dec); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); -#endif return; } @@ -386,21 +356,12 @@ flac_decode(struct decoder * decoder, struct input_stream *input_stream) flac_decode_internal(decoder, input_stream, false); } -#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 bool oggflac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) @@ -421,7 +382,7 @@ oggflac_scan_file(const char *file, if (!(block = FLAC__metadata_iterator_get_block(it))) break; - flac_scan_metadata(NULL, block, + flac_scan_metadata(nullptr, block, handler, handler_ctx); } while (FLAC__metadata_iterator_next(it)); FLAC__metadata_iterator_delete(it); @@ -433,54 +394,57 @@ oggflac_scan_file(const char *file, static void oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) { - if (ogg_stream_type_detect(input_stream) != FLAC) + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_FLAC) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ - input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); flac_decode_internal(decoder, input_stream, true); } -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; static const char *const oggflac_mime_types[] = { "application/ogg", "application/x-ogg", "audio/ogg", "audio/x-flac+ogg", "audio/x-ogg", - NULL + nullptr }; -#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, - .scan_file = oggflac_scan_file, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + nullptr, + nullptr, + oggflac_suffixes, + oggflac_mime_types, }; -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; +static const char *const flac_suffixes[] = { "flac", nullptr }; static const char *const flac_mime_types[] = { "application/flac", "application/x-flac", "audio/flac", "audio/x-flac", - NULL + nullptr }; const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, - .scan_file = flac_scan_file, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + nullptr, + nullptr, + flac_suffixes, + flac_mime_types, }; diff --git a/src/decoder/FLACDecoderPlugin.h b/src/decoder/FLACDecoderPlugin.h new file mode 100644 index 000000000..c99deeef7 --- /dev/null +++ b/src/decoder/FLACDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_DECODER_FLAC_H +#define MPD_DECODER_FLAC_H + +extern const struct decoder_plugin flac_decoder_plugin; +extern const struct decoder_plugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/flac_metadata.c b/src/decoder/FLACMetaData.cxx index bd1eaf323..b1d82a93d 100644 --- a/src/decoder/flac_metadata.c +++ b/src/decoder/FLACMetaData.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,14 @@ */ #include "config.h" -#include "flac_metadata.h" +#include "FLACMetaData.hxx" + +extern "C" { +#include "XiphTags.h" #include "replay_gain_info.h" #include "tag.h" +} + #include "tag_handler.h" #include "tag_table.h" @@ -91,7 +96,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block, int len; const unsigned char *p; - *str = NULL; + *str = nullptr; offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt); if (offset < 0) @@ -136,9 +141,9 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, if (entry->length <= name_length || g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; + return nullptr; - if (char_tnum != NULL) { + if (char_tnum != nullptr) { char_tnum_length = strlen(char_tnum); if (entry->length > name_length + char_tnum_length + 2 && comment[name_length] == '[' && @@ -157,7 +162,7 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, return comment + name_length + 1; } - return NULL; + return nullptr; } /** @@ -174,7 +179,7 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, size_t value_length; value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { + if (value != nullptr) { char *p = g_strndup(value, value_length); tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); g_free(p); @@ -184,23 +189,16 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, return false; } -static const struct tag_table flac_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void flac_scan_comment(const char *char_tnum, const FLAC__StreamMetadata_VorbisComment_Entry *entry, const struct tag_handler *handler, void *handler_ctx) { - if (handler->pair != NULL) { + if (handler->pair != nullptr) { char *name = g_strdup((const char*)entry->entry); char *value = strchr(name, '='); - if (value != NULL && value > name) { + if (value != nullptr && value > name) { *value++ = 0; tag_handler_invoke_pair(handler, handler_ctx, name, value); @@ -209,14 +207,15 @@ flac_scan_comment(const char *char_tnum, g_free(name); } - for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) if (flac_copy_comment(entry, i->name, i->type, char_tnum, handler, handler_ctx)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) if (flac_copy_comment(entry, - tag_item_names[i], i, char_tnum, + tag_item_names[i], (enum tag_type)i, + char_tnum, handler, handler_ctx)) return; } @@ -266,7 +265,7 @@ flac_scan_file2(const char *file, const char *char_tnum, const struct tag_handler *handler, void *handler_ctx) { FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; + FLAC__StreamMetadata *block = nullptr; it = FLAC__metadata_simple_iterator_new(); if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { @@ -307,17 +306,3 @@ flac_scan_file2(const char *file, const char *char_tnum, return true; } - -struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag = tag_new(); - - if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || - tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} diff --git a/src/decoder/flac_metadata.h b/src/decoder/FLACMetaData.hxx index 3c463d5d6..1dbdc9a88 100644 --- a/src/decoder/flac_metadata.h +++ b/src/decoder/FLACMetaData.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -58,7 +58,4 @@ bool flac_scan_file2(const char *file, const char *char_tnum, const struct tag_handler *handler, void *handler_ctx); -struct tag * -flac_tag_load(const char *file, const char *char_tnum); - #endif diff --git a/src/decoder/flac_pcm.c b/src/decoder/FLAC_PCM.cxx index 6964d8ac6..303530aa7 100644 --- a/src/decoder/flac_pcm.c +++ b/src/decoder/FLAC_PCM.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "flac_pcm.h" +#include "FLAC_PCM.hxx" #include <assert.h> diff --git a/src/decoder/flac_pcm.h b/src/decoder/FLAC_PCM.hxx index a931998c1..97d214c17 100644 --- a/src/decoder/flac_pcm.h +++ b/src/decoder/FLAC_PCM.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_FLAC_PCM_H -#define MPD_FLAC_PCM_H +#ifndef MPD_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX #include "audio_format.h" diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx new file mode 100644 index 000000000..99f73d48e --- /dev/null +++ b/src/decoder/OggUtil.cxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2012 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 "OggUtil.hxx" + +extern "C" { +#include "decoder_api.h" +} + +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + struct input_stream *input_stream, size_t size) +{ + char *buffer = ogg_sync_buffer(&oy, size); + if (buffer == nullptr) + return false; + + size_t nbytes = decoder_read(decoder, input_stream, + buffer, size); + if (nbytes == 0) + return false; + + ogg_sync_wrote(&oy, nbytes); + return true; +} + +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + struct decoder *decoder, struct input_stream *input_stream) +{ + while (true) { + int r = ogg_sync_pageout(&oy, &page); + if (r != 0) + return r > 0; + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx new file mode 100644 index 000000000..95bf6472f --- /dev/null +++ b/src/decoder/OggUtil.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_OGG_UTIL_HXX +#define MPD_OGG_UTIL_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Feed data from the #input_stream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, struct decoder *decoder, + struct input_stream *input_stream, size_t size); + +/** + * Feed into the #ogg_sync_state until a page gets available. Garbage + * data at the beginning is considered a fatal error. + * + * @return true if a page is available + */ +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + struct decoder *decoder, struct input_stream *input_stream); + +#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..35e368ca9 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.cxx @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2003-2012 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 "OpusDecoderPlugin.h" +#include "OpusHead.hxx" +#include "OpusTags.hxx" +#include "OggUtil.hxx" + +extern "C" { +#include "ogg_codec.h" +#include "decoder_api.h" +} + +#include "audio_check.h" +#include "tag_handler.h" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <glib.h> + +#include <stdio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "opus" + +static const opus_int32 opus_sample_rate = 48000; + +gcc_pure +static bool +IsOpusHead(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0; +} + +gcc_pure +static bool +IsOpusTags(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; +} + +static bool +mpd_opus_init(G_GNUC_UNUSED const struct config_param *param) +{ + g_debug("%s", opus_get_version_string()); + + return true; +} + +class MPDOpusDecoder { + struct decoder *decoder; + struct input_stream *input_stream; + + ogg_stream_state os; + + OpusDecoder *opus_decoder = nullptr; + opus_int16 *output_buffer = nullptr; + unsigned output_size = 0; + + bool os_initialized = false; + bool found_opus = false; + + int opus_serialno; + + size_t frame_size; + +public: + MPDOpusDecoder(struct decoder *_decoder, + struct input_stream *_input_stream) + :decoder(_decoder), input_stream(_input_stream) {} + ~MPDOpusDecoder(); + + enum decoder_command HandlePage(ogg_page &page); + enum decoder_command HandlePacket(const ogg_packet &packet); + enum decoder_command HandleBOS(const ogg_packet &packet); + enum decoder_command HandleTags(const ogg_packet &packet); + enum decoder_command HandleAudio(const ogg_packet &packet); +}; + +MPDOpusDecoder::~MPDOpusDecoder() +{ + g_free(output_buffer); + + if (opus_decoder != nullptr) + opus_decoder_destroy(opus_decoder); + + if (os_initialized) + ogg_stream_clear(&os); +} + +enum decoder_command +MPDOpusDecoder::HandlePage(ogg_page &page) +{ + const auto page_serialno = ogg_page_serialno(&page); + if (!os_initialized) { + os_initialized = true; + ogg_stream_init(&os, page_serialno); + } else if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + + ogg_packet packet; + while (ogg_stream_packetout(&os, &packet) == 1) { + enum decoder_command cmd = HandlePacket(packet); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +enum decoder_command +MPDOpusDecoder::HandlePacket(const ogg_packet &packet) +{ + if (packet.e_o_s) + return DECODE_COMMAND_STOP; + + if (packet.b_o_s) + return HandleBOS(packet); + else if (!found_opus) + return DECODE_COMMAND_STOP; + + if (IsOpusTags(packet)) + return HandleTags(packet); + + return HandleAudio(packet); +} + +enum decoder_command +MPDOpusDecoder::HandleBOS(const ogg_packet &packet) +{ + assert(packet.b_o_s); + + if (found_opus || !IsOpusHead(packet)) + return DECODE_COMMAND_STOP; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) + return DECODE_COMMAND_STOP; + + assert(opus_decoder == nullptr); + assert(output_buffer == nullptr); + + opus_serialno = os.serialno; + found_opus = true; + + /* TODO: parse attributes from the OpusHead (sample rate, + channels, ...) */ + + int opus_error; + opus_decoder = opus_decoder_create(opus_sample_rate, channels, + &opus_error); + if (opus_decoder == nullptr) { + g_warning("libopus error: %s", + opus_strerror(opus_error)); + return DECODE_COMMAND_STOP; + } + + struct audio_format audio_format; + audio_format_init(&audio_format, opus_sample_rate, + SAMPLE_FORMAT_S16, channels); + decoder_initialized(decoder, &audio_format, false, -1); + frame_size = audio_format_frame_size(&audio_format); + + /* allocate an output buffer for 16 bit PCM samples big enough + to hold a quarter second, larger than 120ms required by + libopus */ + output_size = audio_format.sample_rate / 4; + output_buffer = (opus_int16 *) + g_malloc(sizeof(*output_buffer) * output_size * + audio_format.channels); + + return decoder_get_command(decoder); +} + +enum decoder_command +MPDOpusDecoder::HandleTags(const ogg_packet &packet) +{ + struct tag *tag = tag_new(); + + enum decoder_command cmd; + if (ScanOpusTags(packet.packet, packet.bytes, &add_tag_handler, tag) && + !tag_is_empty(tag)) + cmd = decoder_tag(decoder, input_stream, tag); + else + cmd = decoder_get_command(decoder); + + tag_free(tag); + return cmd; +} + +enum decoder_command +MPDOpusDecoder::HandleAudio(const ogg_packet &packet) +{ + assert(opus_decoder != nullptr); + + int nframes = opus_decode(opus_decoder, + (const unsigned char*)packet.packet, + packet.bytes, + output_buffer, output_size, + 0); + if (nframes < 0) { + g_warning("%s", opus_strerror(nframes)); + return DECODE_COMMAND_STOP; + } + + if (nframes > 0) { + const size_t nbytes = nframes * frame_size; + enum decoder_command cmd = + decoder_data(decoder, input_stream, + output_buffer, nbytes, + 0); + if (cmd != DECODE_COMMAND_NONE) + return cmd; + } + + return DECODE_COMMAND_NONE; +} + +static void +mpd_opus_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_OPUS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream_lock_seek(input_stream, 0, SEEK_SET, nullptr); + + MPDOpusDecoder d(decoder, input_stream); + + ogg_sync_state oy; + ogg_sync_init(&oy); + + while (true) { + if (!OggFeed(oy, decoder, input_stream, 1024)) + break; + + ogg_page page; + while (ogg_sync_pageout(&oy, &page) == 1) { + enum decoder_command cmd = d.HandlePage(page); + if (cmd != DECODE_COMMAND_NONE) + break; + } + } + + ogg_sync_clear(&oy); +} + +static bool +mpd_opus_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) +{ + ogg_sync_state oy; + ogg_sync_init(&oy); + + ogg_page page; + if (!OggExpectPage(oy, page, nullptr, is)) { + ogg_sync_clear(&oy); + return false; + } + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + ogg_stream_state os; + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + + ogg_packet packet; + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r < 0) { + result = false; + break; + } + + if (r == 0) { + if (remaining_pages-- == 0) + break; + + if (!OggExpectPage(oy, page, nullptr, is)) { + result = false; + break; + } + + ogg_stream_pagein(&os, &page); + continue; + } + + if (packet.b_o_s) { + if (!IsOpusHead(packet)) + break; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) { + result = false; + break; + } + + result = true; + } else if (!result) + break; + else if (IsOpusTags(packet)) { + if (!ScanOpusTags(packet.packet, packet.bytes, + handler, handler_ctx)) + result = false; + + break; + } + } + + ogg_stream_clear(&os); + ogg_sync_clear(&oy); + + return result; +} + +static const char *const opus_suffixes[] = { + "opus", + "ogg", + "oga", + nullptr +}; + +static const char *const opus_mime_types[] = { + "audio/opus", + nullptr +}; + +const struct decoder_plugin opus_decoder_plugin = { + "opus", + mpd_opus_init, + nullptr, + mpd_opus_stream_decode, + nullptr, + nullptr, + mpd_opus_scan_stream, + nullptr, + opus_suffixes, + opus_mime_types, +}; diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/OpusDecoderPlugin.h new file mode 100644 index 000000000..c95d6ded3 --- /dev/null +++ b/src/decoder/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_DECODER_OPUS_H +#define MPD_DECODER_OPUS_H + +extern const struct decoder_plugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx new file mode 100644 index 000000000..c57e08e10 --- /dev/null +++ b/src/decoder/OpusHead.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2012 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 "OpusHead.hxx" + +#include <stdint.h> +#include <string.h> + +struct OpusHead { + char signature[8]; + uint8_t version, channels; + uint16_t pre_skip; + uint32_t sample_rate; + uint16_t output_gain; + uint8_t channel_mapping; +}; + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) +{ + const OpusHead *h = (const OpusHead *)data; + if (size < 19 || (h->version & 0xf0) != 0) + return false; + + channels_r = h->channels; + return true; +} diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx new file mode 100644 index 000000000..9f75c4f70 --- /dev/null +++ b/src/decoder/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_OPUS_HEAD_HXX +#define MPD_OPUS_HEAD_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); + +#endif diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx new file mode 100644 index 000000000..2cfc14118 --- /dev/null +++ b/src/decoder/OpusReader.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_OPUS_READER_HXX +#define MPD_OPUS_READER_HXX + +#include "check.h" + +#include <stdint.h> +#include <string.h> + +class OpusReader { + const uint8_t *p, *const end; + +public: + OpusReader(const void *_p, size_t size) + :p((const uint8_t *)_p), end(p + size) {} + + bool Skip(size_t length) { + p += length; + return p <= end; + } + + const void *Read(size_t length) { + const uint8_t *result = p; + return Skip(length) + ? result + : nullptr; + } + + bool Expect(const void *value, size_t length) { + const void *data = Read(length); + return data != nullptr && memcmp(value, data, length) == 0; + } + + bool ReadByte(uint8_t &value_r) { + if (p >= end) + return false; + + value_r = *p++; + return true; + } + + bool ReadShort(uint16_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8); + return true; + } + + bool ReadWord(uint32_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8) + | (value[2] << 16) | (value[3] << 24); + return true; + } + + bool SkipString() { + uint32_t length; + return ReadWord(length) && Skip(length); + } + + char *ReadString() { + uint32_t length; + if (!ReadWord(length)) + return nullptr; + + const char *src = (const char *)Read(length); + if (src == nullptr) + return nullptr; + + return strndup(src, length); + } +}; + +#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx new file mode 100644 index 000000000..cb35a6247 --- /dev/null +++ b/src/decoder/OpusTags.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2012 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 "OpusTags.hxx" +#include "OpusReader.hxx" +#include "XiphTags.h" +#include "tag_handler.h" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +static void +ScanOneOpusTag(const char *name, const char *value, + const struct tag_handler *handler, void *ctx) +{ + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + enum tag_type t = tag_table_lookup_i(xiph_tags, name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx) +{ + OpusReader r(data, size); + if (!r.Expect("OpusTags", 8)) + return false; + + if (handler->pair == nullptr && handler->tag == nullptr) + return true; + + if (!r.SkipString()) + return false; + + uint32_t n; + if (!r.ReadWord(n)) + return false; + + while (n-- > 0) { + char *p = r.ReadString(); + if (p == nullptr) + return false; + + char *eq = strchr(p, '='); + if (eq != nullptr && eq > p) { + *eq = 0; + + ScanOneOpusTag(p, eq + 1, handler, ctx); + } + + free(p); + } + + return true; +} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx new file mode 100644 index 000000000..2f3eec844 --- /dev/null +++ b/src/decoder/OpusTags.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_OPUS_TAGS_HXX +#define MPD_OPUS_TAGS_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusTags(const void *data, size_t size, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/XiphTags.c b/src/decoder/XiphTags.c new file mode 100644 index 000000000..d55787b94 --- /dev/null +++ b/src/decoder/XiphTags.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2012 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 "XiphTags.h" + +const struct tag_table xiph_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/decoder/XiphTags.h b/src/decoder/XiphTags.h new file mode 100644 index 000000000..22a4e2204 --- /dev/null +++ b/src/decoder/XiphTags.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2012 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. + */ + +#ifndef MPD_XIPH_TAGS_H +#define MPD_XIPH_TAGS_H + +#include "check.h" +#include "tag_table.h" + +extern const struct tag_table xiph_tags[]; + +#endif diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h deleted file mode 100644 index 9a30acc26..000000000 --- a/src/decoder/flac_compat.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2011 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. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#ifndef MPD_FLAC_COMPAT_H -#define MPD_FLAC_COMPAT_H - -#include <FLAC/export.h> -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -# include <FLAC/seekable_stream_decoder.h> - -/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been - merged into the StreamDecoder. The following macros try to emulate - the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls - to the old SeekableStreamDecoder API. */ - -#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder -#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new -#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position -#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state -#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single -#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata -#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute -#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish -#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete - -#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM - -typedef unsigned flac_read_status_size_t; - -#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus -#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR - -#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus -#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK -#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR -#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR - -#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus -#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK -#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR -#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR - -#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus -#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK -#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR -#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR - -typedef enum { - FLAC__STREAM_DECODER_INIT_STATUS_OK, - FLAC__STREAM_DECODER_INIT_STATUS_ERROR, -} FLAC__StreamDecoderInitStatus; - -static inline FLAC__StreamDecoderInitStatus -FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder, - FLAC__SeekableStreamDecoderReadCallback read_cb, - FLAC__SeekableStreamDecoderSeekCallback seek_cb, - FLAC__SeekableStreamDecoderTellCallback tell_cb, - FLAC__SeekableStreamDecoderLengthCallback length_cb, - FLAC__SeekableStreamDecoderEofCallback eof_cb, - FLAC__SeekableStreamDecoderWriteCallback write_cb, - FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, - FLAC__SeekableStreamDecoderErrorCallback error_cb, - void *data) -{ - return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) && - FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) && - FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) && - FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) && - FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) && - FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) && - FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) && - FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) && - FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) && - FLAC__seekable_stream_decoder_set_client_data(decoder, data) && - FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK - ? FLAC__STREAM_DECODER_INIT_STATUS_OK - : FLAC__STREAM_DECODER_INIT_STATUS_ERROR; -} - -#else /* FLAC_API_VERSION_CURRENT > 7 */ - -# include <FLAC/stream_decoder.h> - -# define flac_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) - -typedef size_t flac_read_status_size_t; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c index 62c371642..c1a0c4b0f 100644 --- a/src/decoder/mad_decoder_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -76,9 +76,9 @@ mad_fixed_to_24_sample(mad_fixed_t sample) sample = sample + (1L << (MAD_F_FRACBITS - bits)); /* clip */ - if (sample > MAX) + if (gcc_unlikely(sample > MAX)) sample = MAX; - else if (sample < MIN) + else if (gcc_unlikely(sample < MIN)) sample = MIN; /* quantize */ diff --git a/src/decoder/_ogg_common.c b/src/decoder/ogg_codec.c index 09d2712da..7416f27da 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/ogg_codec.c @@ -22,25 +22,27 @@ */ #include "config.h" -#include "_ogg_common.h" +#include "ogg_codec.h" -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is) { /* oggflac detection based on code in ogg123 and this post * http://lists.xiph.org/pipermail/flac/2004-December/000393.html * ogg123 trunk still doesn't have this patch as of June 2005 */ unsigned char buf[41]; - size_t r; - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return VORBIS; + return OGG_CODEC_UNKNOWN; if ((memcmp(buf + 29, "FLAC", 4) == 0 && memcmp(buf + 37, "fLaC", 4) == 0) || memcmp(buf + 28, "FLAC", 4) == 0 || memcmp(buf + 28, "fLaC", 4) == 0) - return FLAC; + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; - return VORBIS; + return OGG_CODEC_VORBIS; } diff --git a/src/decoder/_ogg_common.h b/src/decoder/ogg_codec.h index 85e4ebba6..fd1fecfbb 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/ogg_codec.h @@ -21,13 +21,19 @@ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) */ -#ifndef MPD_OGG_COMMON_H -#define MPD_OGG_COMMON_H +#ifndef MPD_OGG_CODEC_H +#define MPD_OGG_CODEC_H #include "decoder_api.h" -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; -ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream); +enum ogg_codec +ogg_codec_detect(struct decoder *decoder, struct input_stream *is); #endif /* _OGG_COMMON_H */ diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 5d162f179..de2e599e9 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -104,7 +104,7 @@ sidplay_init(const struct config_param *param) return true; } -void +static void sidplay_finish() { g_pattern_spec_free(path_with_subtune); @@ -136,7 +136,7 @@ get_container_name(const char *path_fs) * returns tune number from file.sid/tune_xxx.sid style path or 1 if * no subtune is appended */ -static int +static unsigned get_song_num(const char *path_fs) { if(g_pattern_match(path_with_subtune, @@ -172,7 +172,7 @@ get_song_length(const char *path_fs) char md5sum[SIDTUNE_MD5_LENGTH+1]; tune.createMD5(md5sum); - int song_num=get_song_num(path_fs); + const unsigned song_num = get_song_num(path_fs); gsize num_items; gchar **values=g_key_file_get_string_list(songlength_database, @@ -330,7 +330,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) decoder_command_finished(decoder); } - if (song_len > 0 && player.time() >= song_len) + if (song_len > 0 && player.time() >= (unsigned)song_len) break; } while (cmd != DECODE_COMMAND_STOP); diff --git a/src/decoder/vorbis_comments.c b/src/decoder/vorbis_comments.c index 6c2d57b72..84f7c5014 100644 --- a/src/decoder/vorbis_comments.c +++ b/src/decoder/vorbis_comments.c @@ -19,6 +19,7 @@ #include "config.h" #include "vorbis_comments.h" +#include "XiphTags.h" #include "tag.h" #include "tag_table.h" #include "tag_handler.h" @@ -95,13 +96,6 @@ vorbis_copy_comment(const char *comment, return false; } -static const struct tag_table vorbis_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { NULL, TAG_NUM_OF_ITEM_TYPES } -}; - static void vorbis_scan_comment(const char *comment, const struct tag_handler *handler, void *handler_ctx) @@ -119,7 +113,7 @@ vorbis_scan_comment(const char *comment, g_free(name); } - for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) + for (const struct tag_table *i = xiph_tags; i->name != NULL; ++i) if (vorbis_copy_comment(comment, i->name, i->type, handler, handler_ctx)) return; diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c index 15cdc0ca9..f180a6559 100644 --- a/src/decoder/vorbis_decoder_plugin.c +++ b/src/decoder/vorbis_decoder_plugin.c @@ -19,7 +19,7 @@ #include "config.h" #include "vorbis_comments.h" -#include "_ogg_common.h" +#include "ogg_codec.h" #include "audio_check.h" #include "uri.h" #include "tag_handler.h" @@ -48,12 +48,11 @@ #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 +#define VORBIS_BIG_ENDIAN true #else -#define OGG_DECODE_USE_BIGENDIAN 0 +#define VORBIS_BIG_ENDIAN false #endif struct vorbis_input_stream { @@ -66,9 +65,8 @@ struct vorbis_input_stream { static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { struct vorbis_input_stream *vis = data; - size_t ret; - - ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); errno = 0; @@ -155,9 +153,7 @@ static void vorbis_send_comments(struct decoder *decoder, struct input_stream *is, char **comments) { - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); + struct tag *tag = vorbis_comments_to_tag(comments); if (!tag) return; @@ -165,55 +161,79 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is, tag_free(tag); } +#ifndef HAVE_TREMOR +static void +vorbis_interleave(float *dest, const float *const*src, + unsigned nframes, unsigned channels) +{ + for (const float *const*src_end = src + channels; + src != src_end; ++src, ++dest) { + float *d = dest; + for (const float *s = *src, *s_end = s + nframes; + s != s_end; ++s, d += channels) + *d = *s; + } +} +#endif + /* public */ static void vorbis_stream_decode(struct decoder *decoder, struct input_stream *input_stream) { GError *error = NULL; - OggVorbis_File vf; - struct vorbis_input_stream vis; - 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) + + if (ogg_codec_detect(decoder, input_stream) != OGG_CODEC_VORBIS) return; - /* rewind the stream, because ogg_stream_type_detect() has + /* rewind the stream, because ogg_codec_detect() has moved it */ input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); + struct vorbis_input_stream vis; + OggVorbis_File vf; if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) return; - vi = ov_info(&vf, -1); + const vorbis_info *vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); return; } + struct audio_format audio_format; if (!audio_format_init_checked(&audio_format, vi->rate, +#ifdef HAVE_TREMOR SAMPLE_FORMAT_S16, +#else + SAMPLE_FORMAT_FLOAT, +#endif vi->channels, &error)) { g_warning("%s", error->message); g_error_free(error); return; } - total_time = ov_time_total(&vf, -1); + float total_time = ov_time_total(&vf, -1); if (total_time < 0) total_time = 0; decoder_initialized(decoder, &audio_format, vis.seekable, total_time); + enum decoder_command cmd = decoder_get_command(decoder); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + G_N_ELEMENTS(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + do { if (cmd == DECODE_COMMAND_SEEK) { double seek_where = decoder_seek_where(decoder); @@ -223,17 +243,33 @@ vorbis_stream_decode(struct decoder *decoder, 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) + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + VORBIS_BIG_ENDIAN, 2, 1, + ¤t_section); +#else + float **per_channel; + long nframes = ov_read_float(&vf, &per_channel, + frames_per_buffer, + ¤t_section); + long nbytes = nframes; + if (nframes > 0) { + vorbis_interleave(buffer, + (const float*const*)per_channel, + nframes, audio_format.channels); + nbytes *= frame_size; + } +#endif + + if (nbytes == OV_HOLE) /* bad packet */ + nbytes = 0; + else if (nbytes <= 0) /* break on EOF or other error */ break; if (current_section != prev_section) { - char **comments; - vi = ov_info(&vf, -1); if (vi == NULL) { g_warning("ov_info() has failed"); @@ -248,7 +284,7 @@ vorbis_stream_decode(struct decoder *decoder, break; } - comments = ov_comment(&vf, -1)->user_comments; + char **comments = ov_comment(&vf, -1)->user_comments; vorbis_send_comments(decoder, input_stream, comments); struct replay_gain_info rgi; @@ -258,12 +294,13 @@ vorbis_stream_decode(struct decoder *decoder, prev_section = current_section; } - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; cmd = decoder_data(decoder, input_stream, - chunk, ret, - bitRate); + buffer, nbytes, + kbit_rate); } while (cmd != DECODE_COMMAND_STOP); ov_clear(&vf); |