diff options
Diffstat (limited to '')
175 files changed, 14440 insertions, 11275 deletions
diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx deleted file mode 100644 index c79fca5f9..000000000 --- a/src/decoder/AdPlugDecoderPlugin.cxx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2013 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/TagHandler.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <adplug/adplug.h> -#include <adplug/emuopl.h> - -#include <assert.h> - -static unsigned sample_rate; - -static bool -adplug_init(const config_param ¶m) -{ - Error error; - - sample_rate = param.GetBlockValue("sample_rate", 48000u); - if (!audio_check_sample_rate(sample_rate, error)) { - LogError(error); - return false; - } - - return true; -} - -static void -adplug_file_decode(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; - - const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, false, - player->songlength() / 1000.); - - int16_t buffer[2048]; - const unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2; - DecoderCommand cmd; - - do { - if (!player->update()) - break; - - opl.update(buffer, frames_per_buffer); - cmd = decoder_data(decoder, nullptr, - buffer, sizeof(buffer), - 0); - } while (cmd == DecoderCommand::NONE); - - delete player; -} - -static void -adplug_scan_tag(TagType 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 DecoderPlugin 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 deleted file mode 100644 index a827fdc7d..000000000 --- a/src/decoder/AdPlugDecoderPlugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 DecoderPlugin adplug_decoder_plugin; - -#endif diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx deleted file mode 100644 index b1b8bf613..000000000 --- a/src/decoder/AudiofileDecoderPlugin.cxx +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "AudiofileDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <audiofile.h> -#include <af_vfs.h> - -#include <assert.h> -#include <stdio.h> - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -static constexpr Domain audiofile_domain("audiofile"); - -struct AudioFileInputStream { - Decoder *const decoder; - InputStream &is; - - size_t Read(void *buffer, size_t size) { - /* libaudiofile does not like partial reads at all, - and will abort playback; therefore always force full - reads */ - return decoder_read_full(decoder, is, buffer, size) - ? size - : 0; - } -}; - -static int audiofile_get_duration(const char *file) -{ - int total_time; - AFfilehandle af_fp = afOpenFile(file, "r", nullptr); - 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) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - - return afis.Read(data, length); -} - -static AFfileoffset -audiofile_file_length(AFvirtualfile *vfile) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - InputStream &is = afis.is; - - return is.GetSize(); -} - -static AFfileoffset -audiofile_file_tell(AFvirtualfile *vfile) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - InputStream &is = afis.is; - - return is.GetOffset(); -} - -static void -audiofile_file_destroy(AFvirtualfile *vfile) -{ - assert(vfile->closure != nullptr); - - vfile->closure = nullptr; -} - -static AFfileoffset -audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - InputStream &is = afis.is; - - int whence = (is_relative ? SEEK_CUR : SEEK_SET); - - Error error; - if (is.LockSeek(offset, whence, error)) { - return is.GetOffset(); - } else { - LogError(error, "Seek failed"); - return -1; - } -} - -static AFvirtualfile * -setup_virtual_fops(AudioFileInputStream &afis) -{ - AFvirtualfile *vf = new AFvirtualfile(); - vf->closure = &afis; - vf->write = nullptr; - 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 SampleFormat -audiofile_bits_to_sample_format(int bits) -{ - switch (bits) { - case 8: - return SampleFormat::S8; - - case 16: - return SampleFormat::S16; - - case 24: - return SampleFormat::S24_P32; - - case 32: - return SampleFormat::S32; - } - - return SampleFormat::UNDEFINED; -} - -static SampleFormat -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))) { - FormatDebug(audiofile_domain, - "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(Decoder &decoder, InputStream &is) -{ - AFvirtualfile *vf; - int fs, frame_count; - AFfilehandle af_fp; - AudioFormat audio_format; - float total_time; - uint16_t bit_rate; - int ret; - char chunk[CHUNK_SIZE]; - - if (!is.IsSeekable()) { - LogWarning(audiofile_domain, "not seekable"); - return; - } - - AudioFileInputStream afis{&decoder, is}; - vf = setup_virtual_fops(afis); - - af_fp = afOpenVirtualFile(vf, "r", nullptr); - if (af_fp == AF_NULL_FILEHANDLE) { - LogWarning(audiofile_domain, "failed to input stream"); - return; - } - - Error error; - 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)) { - LogError(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.GetSize() * 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); - - DecoderCommand cmd; - do { - ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, - CHUNK_SIZE / fs); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, nullptr, - chunk, ret * fs, - bit_rate); - - if (cmd == DecoderCommand::SEEK) { - AFframecount frame = decoder_seek_where(decoder) * - audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); - - decoder_command_finished(decoder); - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); - - afCloseFile(af_fp); -} - -static bool -audiofile_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - int total_time = audiofile_get_duration(file); - - if (total_time < 0) { - FormatWarning(audiofile_domain, - "Failed to get total song time from: %s", - file); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const audiofile_suffixes[] = { - "wav", "au", "aiff", "aif", nullptr -}; - -static const char *const audiofile_mime_types[] = { - "audio/x-wav", - "audio/x-aiff", - nullptr -}; - -const struct DecoderPlugin audiofile_decoder_plugin = { - "audiofile", - nullptr, - nullptr, - audiofile_stream_decode, - nullptr, - audiofile_scan_file, - nullptr, - nullptr, - audiofile_suffixes, - audiofile_mime_types, -}; diff --git a/src/decoder/AudiofileDecoderPlugin.hxx b/src/decoder/AudiofileDecoderPlugin.hxx deleted file mode 100644 index 5a17281b0..000000000 --- a/src/decoder/AudiofileDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_AUDIOFILE_HXX -#define MPD_DECODER_AUDIOFILE_HXX - -extern const struct DecoderPlugin audiofile_decoder_plugin; - -#endif diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx new file mode 100644 index 000000000..4794d60e7 --- /dev/null +++ b/src/decoder/DecoderAPI.cxx @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderAPI.hxx" +#include "DecoderError.hxx" +#include "pcm/PcmConvert.hxx" +#include "AudioConfig.hxx" +#include "ReplayGainConfig.hxx" +#include "MusicChunk.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DetachedSong.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "util/ConstBuffer.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <math.h> + +void +decoder_initialized(Decoder &decoder, + const AudioFormat audio_format, + bool seekable, SignedSongTime duration) +{ + DecoderControl &dc = decoder.dc; + struct audio_format_string af_string; + + assert(dc.state == DecoderState::START); + assert(dc.pipe != nullptr); + assert(dc.pipe->IsEmpty()); + assert(decoder.convert == nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(!decoder.seeking); + assert(audio_format.IsDefined()); + assert(audio_format.IsValid()); + + dc.in_audio_format = audio_format; + dc.out_audio_format = getOutputAudioFormat(audio_format); + + dc.seekable = seekable; + dc.total_time = duration; + + FormatDebug(decoder_domain, "audio_format=%s, seekable=%s", + audio_format_to_string(dc.in_audio_format, &af_string), + seekable ? "true" : "false"); + + if (dc.in_audio_format != dc.out_audio_format) { + FormatDebug(decoder_domain, "converting to %s", + audio_format_to_string(dc.out_audio_format, + &af_string)); + + decoder.convert = new PcmConvert(); + + Error error; + if (!decoder.convert->Open(dc.in_audio_format, + dc.out_audio_format, + error)) + decoder.error = std::move(error); + } + + dc.Lock(); + dc.state = DecoderState::DECODE; + dc.client_cond.signal(); + dc.Unlock(); +} + +/** + * Checks if we need an "initial seek". If so, then the initial seek + * is prepared, and the function returns true. + */ +gcc_pure +static bool +decoder_prepare_initial_seek(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + if (dc.state != DecoderState::DECODE) + /* wait until the decoder has finished initialisation + (reading file headers etc.) before emitting the + virtual "SEEK" command */ + return false; + + if (decoder.initial_seek_running) + /* initial seek has already begun - override any other + command */ + return true; + + if (decoder.initial_seek_pending) { + if (!dc.seekable) { + /* seeking is not possible */ + decoder.initial_seek_pending = false; + return false; + } + + if (dc.command == DecoderCommand::NONE) { + /* begin initial seek */ + + decoder.initial_seek_pending = false; + decoder.initial_seek_running = true; + return true; + } + + /* skip initial seek when there's another command + (e.g. STOP) */ + + decoder.initial_seek_pending = false; + } + + return false; +} + +/** + * Returns the current decoder command. May return a "virtual" + * synthesized command, e.g. to seek to the beginning of the CUE + * track. + */ +gcc_pure +static DecoderCommand +decoder_get_virtual_command(Decoder &decoder) +{ + if (decoder.error.IsDefined()) + /* an error has occurred: stop the decoder plugin */ + return DecoderCommand::STOP; + + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + if (decoder_prepare_initial_seek(decoder)) + return DecoderCommand::SEEK; + + return dc.command; +} + +DecoderCommand +decoder_get_command(Decoder &decoder) +{ + return decoder_get_virtual_command(decoder); +} + +void +decoder_command_finished(Decoder &decoder) +{ + DecoderControl &dc = decoder.dc; + + dc.Lock(); + + assert(dc.command != DecoderCommand::NONE || + decoder.initial_seek_running); + assert(dc.command != DecoderCommand::SEEK || + decoder.initial_seek_running || + dc.seek_error || decoder.seeking); + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) { + assert(!decoder.seeking); + assert(decoder.chunk == nullptr); + assert(dc.pipe->IsEmpty()); + + decoder.initial_seek_running = false; + decoder.timestamp = dc.start_time.ToDoubleS(); + dc.Unlock(); + return; + } + + if (decoder.seeking) { + decoder.seeking = false; + + /* delete frames from the old song position */ + + if (decoder.chunk != nullptr) { + dc.buffer->Return(decoder.chunk); + decoder.chunk = nullptr; + } + + dc.pipe->Clear(*dc.buffer); + + decoder.timestamp = dc.seek_time.ToDoubleS(); + } + + dc.command = DecoderCommand::NONE; + dc.client_cond.signal(); + dc.Unlock(); +} + +SongTime +decoder_seek_time(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) + return dc.start_time; + + assert(dc.command == DecoderCommand::SEEK); + + decoder.seeking = true; + + return dc.seek_time; +} + +uint64_t +decoder_seek_where_frame(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + + return decoder_seek_time(decoder).ToScale<uint64_t>(dc.in_audio_format.sample_rate); +} + +void decoder_seek_error(Decoder & decoder) +{ + DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) { + /* d'oh, we can't seek to the sub-song start position, + what now? - no idea, ignoring the problem for now. */ + decoder.initial_seek_running = false; + return; + } + + assert(dc.command == DecoderCommand::SEEK); + + dc.seek_error = true; + decoder.seeking = false; + + decoder_command_finished(decoder); +} + +InputStream * +decoder_open_uri(Decoder &decoder, const char *uri, Error &error) +{ + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + DecoderControl &dc = decoder.dc; + Mutex &mutex = dc.mutex; + Cond &cond = dc.cond; + + InputStream *is = InputStream::Open(uri, mutex, cond, error); + if (is == nullptr) + return nullptr; + + mutex.lock(); + while (true) { + is->Update(); + if (is->IsReady()) { + mutex.unlock(); + return is; + } + + if (dc.command == DecoderCommand::STOP) { + mutex.unlock(); + delete is; + return nullptr; + } + + cond.wait(mutex); + } +} + +/** + * Should be read operation be cancelled? That is the case when the + * player thread has sent a command such as "STOP". + */ +gcc_pure +static inline bool +decoder_check_cancel_read(const Decoder *decoder) +{ + if (decoder == nullptr) + return false; + + const DecoderControl &dc = decoder->dc; + if (dc.command == DecoderCommand::NONE) + return false; + + /* ignore the SEEK command during initialization, the plugin + should handle that after it has initialized successfully */ + if (dc.command == DecoderCommand::SEEK && + (dc.state == DecoderState::START || decoder->seeking)) + return false; + + return true; +} + +size_t +decoder_read(Decoder *decoder, + InputStream &is, + void *buffer, size_t length) +{ + /* XXX don't allow decoder==nullptr */ + + assert(decoder == nullptr || + decoder->dc.state == DecoderState::START || + decoder->dc.state == DecoderState::DECODE); + assert(buffer != nullptr); + + if (length == 0) + return 0; + + is.Lock(); + + while (true) { + if (decoder_check_cancel_read(decoder)) { + is.Unlock(); + return 0; + } + + if (is.IsAvailable()) + break; + + is.cond.wait(is.mutex); + } + + Error error; + size_t nbytes = is.Read(buffer, length, error); + assert(nbytes == 0 || !error.IsDefined()); + assert(nbytes > 0 || error.IsDefined() || is.IsEOF()); + + is.Unlock(); + + if (gcc_unlikely(nbytes == 0 && error.IsDefined())) + LogError(error); + + return nbytes; +} + +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *_buffer, size_t size) +{ + uint8_t *buffer = (uint8_t *)_buffer; + + while (size > 0) { + size_t nbytes = decoder_read(decoder, is, buffer, size); + if (nbytes == 0) + return false; + + buffer += nbytes; + size -= nbytes; + } + + return true; +} + +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size) +{ + while (size > 0) { + char buffer[1024]; + size_t nbytes = decoder_read(decoder, is, buffer, + std::min(sizeof(buffer), size)); + if (nbytes == 0) + return false; + + size -= nbytes; + } + + return true; +} + +void +decoder_timestamp(Decoder &decoder, double t) +{ + assert(t >= 0); + + decoder.timestamp = t; +} + +/** + * Sends a #tag as-is to the music pipe. Flushes the current chunk + * (decoder.chunk) if there is one. + */ +static DecoderCommand +do_send_tag(Decoder &decoder, const Tag &tag) +{ + MusicChunk *chunk; + + if (decoder.chunk != nullptr) { + /* there is a partial chunk - flush it, we want the + tag in a new chunk */ + decoder.FlushChunk(); + } + + assert(decoder.chunk == nullptr); + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(decoder.dc.command != DecoderCommand::NONE); + return decoder.dc.command; + } + + chunk->tag = new Tag(tag); + return DecoderCommand::NONE; +} + +static bool +update_stream_tag(Decoder &decoder, InputStream *is) +{ + Tag *tag; + + tag = is != nullptr + ? is->LockReadTag() + : nullptr; + if (tag == nullptr) { + tag = decoder.song_tag; + if (tag == nullptr) + return false; + + /* no stream tag present - submit the song tag + instead */ + decoder.song_tag = nullptr; + } + + delete decoder.stream_tag; + decoder.stream_tag = tag; + return true; +} + +DecoderCommand +decoder_data(Decoder &decoder, + InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + assert(length % dc.in_audio_format.GetFrameSize() == 0); + + dc.Lock(); + cmd = decoder_get_virtual_command(decoder); + dc.Unlock(); + + if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK || + length == 0) + return cmd; + + assert(!decoder.initial_seek_pending); + assert(!decoder.initial_seek_running); + + /* send stream tags */ + + if (update_stream_tag(decoder, is)) { + if (decoder.decoder_tag != nullptr) { + /* merge with tag from decoder plugin */ + Tag *tag = Tag::Merge(*decoder.decoder_tag, + *decoder.stream_tag); + cmd = do_send_tag(decoder, *tag); + delete tag; + } else + /* send only the stream tag */ + cmd = do_send_tag(decoder, *decoder.stream_tag); + + if (cmd != DecoderCommand::NONE) + return cmd; + } + + if (decoder.convert != nullptr) { + assert(dc.in_audio_format != dc.out_audio_format); + + Error error; + auto result = decoder.convert->Convert({data, length}, + error); + if (data == nullptr) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + LogError(error); + return DecoderCommand::STOP; + } + + data = result.data; + length = result.size; + } else { + assert(dc.in_audio_format == dc.out_audio_format); + } + + while (length > 0) { + MusicChunk *chunk; + bool full; + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(dc.command != DecoderCommand::NONE); + return dc.command; + } + + const auto dest = + chunk->Write(dc.out_audio_format, + SongTime::FromS(decoder.timestamp) - + dc.song->GetStartTime(), + kbit_rate); + if (dest.IsEmpty()) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + continue; + } + + const size_t nbytes = std::min(dest.size, length); + + /* copy the buffer */ + + memcpy(dest.data, data, nbytes); + + /* expand the music pipe chunk */ + + full = chunk->Expand(dc.out_audio_format, nbytes); + if (full) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + } + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + + decoder.timestamp += (double)nbytes / + dc.out_audio_format.GetTimeToSize(); + + if (dc.end_time.IsPositive() && + decoder.timestamp >= dc.end_time.ToDoubleS()) + /* the end of this range has been reached: + stop decoding */ + return DecoderCommand::STOP; + } + + return DecoderCommand::NONE; +} + +DecoderCommand +decoder_tag(Decoder &decoder, InputStream *is, + Tag &&tag) +{ + gcc_unused const DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + + /* save the tag */ + + delete decoder.decoder_tag; + decoder.decoder_tag = new Tag(tag); + + /* check for a new stream tag */ + + update_stream_tag(decoder, is); + + /* check if we're seeking */ + + if (decoder_prepare_initial_seek(decoder)) + /* during initial seek, no music chunk must be created + until seeking is finished; skip the rest of the + function here */ + return DecoderCommand::SEEK; + + /* send tag to music pipe */ + + if (decoder.stream_tag != nullptr) { + /* merge with tag from input stream */ + Tag *merged; + + merged = Tag::Merge(*decoder.stream_tag, + *decoder.decoder_tag); + cmd = do_send_tag(decoder, *merged); + delete merged; + } else + /* send only the decoder tag */ + cmd = do_send_tag(decoder, *decoder.decoder_tag); + + return cmd; +} + +void +decoder_replay_gain(Decoder &decoder, + const ReplayGainInfo *replay_gain_info) +{ + if (replay_gain_info != nullptr) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + ReplayGainMode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + const auto &tuple = replay_gain_info->tuples[rgm]; + const auto scale = + tuple.CalculateScale(replay_gain_preamp, + replay_gain_missing_preamp, + replay_gain_limit); + decoder.dc.replay_gain_db = 20.0 * log10f(scale); + } + + decoder.replay_gain_info = *replay_gain_info; + decoder.replay_gain_serial = serial; + + if (decoder.chunk != nullptr) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder.FlushChunk(); + } + } else + decoder.replay_gain_serial = 0; +} + +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp) +{ + DecoderControl &dc = decoder.dc; + + dc.SetMixRamp(std::move(mix_ramp)); +} diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx new file mode 100644 index 000000000..b756331d9 --- /dev/null +++ b/src/decoder/DecoderAPI.hxx @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/*! \file + * \brief The MPD Decoder API + * + * This is the public API which is used by decoder plugins to + * communicate with the mpd core. + */ + +#ifndef MPD_DECODER_API_HXX +#define MPD_DECODER_API_HXX + +// IWYU pragma: begin_exports + +#include "check.h" +#include "DecoderCommand.hxx" +#include "DecoderPlugin.hxx" +#include "ReplayGainInfo.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "config/ConfigData.hxx" +#include "Chrono.hxx" + +// IWYU pragma: end_exports + +#include <stdint.h> + +class Error; + +/** + * Notify the player thread that it has finished initialization and + * that it has read the song's meta data. + * + * @param decoder the decoder object + * @param audio_format the audio format which is going to be sent to + * decoder_data() + * @param seekable true if the song is seekable + * @param duration the total duration of this song; negative if + * unknown + */ +void +decoder_initialized(Decoder &decoder, + AudioFormat audio_format, + bool seekable, SignedSongTime duration); + +/** + * Determines the pending decoder command. + * + * @param decoder the decoder object + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +gcc_pure +DecoderCommand +decoder_get_command(Decoder &decoder); + +/** + * Called by the decoder when it has performed the requested command + * (dc->command). This function resets dc->command and wakes up the + * player thread. + * + * @param decoder the decoder object + */ +void +decoder_command_finished(Decoder &decoder); + +/** + * Call this when you have received the DecoderCommand::SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the seek in milliseconds + */ +gcc_pure +SongTime +decoder_seek_time(Decoder &decoder); + +/** + * Call this when you have received the DecoderCommand::SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the seek in frames + */ +gcc_pure +uint64_t +decoder_seek_where_frame(Decoder &decoder); + +/** + * Call this instead of decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +void +decoder_seek_error(Decoder &decoder); + +/** + * Open a new #InputStream and wait until it's ready. Can get + * cancelled by DecoderCommand::STOP (returns nullptr without setting + * #Error). + */ +InputStream * +decoder_open_uri(Decoder &decoder, const char *uri, Error &error); + +/** + * Blocking read from the input stream. + * + * @param decoder the decoder object + * @param is the input stream to read from + * @param buffer the destination buffer + * @param length the maximum number of bytes to read + * @return the number of bytes read, or 0 if one of the following + * occurs: end of file; error; command (like SEEK or STOP). + */ +size_t +decoder_read(Decoder *decoder, InputStream &is, + void *buffer, size_t length); + +static inline size_t +decoder_read(Decoder &decoder, InputStream &is, + void *buffer, size_t length) +{ + return decoder_read(&decoder, is, buffer, length); +} + +/** + * Blocking read from the input stream. Attempts to fill the buffer + * completely; there is no partial result. + * + * @return true on success, false on error or command or not enough + * data + */ +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *buffer, size_t size); + +/** + * Skip data on the #InputStream. + * + * @return true on success, false on error or command + */ +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size); + +/** + * Sets the time stamp for the next data chunk [seconds]. The MPD + * core automatically counts it up, and a decoder plugin only needs to + * use this function if it thinks that adding to the time stamp based + * on the buffer size won't work. + */ +void +decoder_timestamp(Decoder &decoder, double t); + +/** + * This function is called by the decoder plugin when it has + * successfully decoded block of input data. + * + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param data the source buffer + * @param length the number of bytes in the buffer + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +decoder_data(Decoder &decoder, InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate); + +static inline DecoderCommand +decoder_data(Decoder &decoder, InputStream &is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + return decoder_data(decoder, &is, data, length, kbit_rate); +} + +/** + * This function is called by the decoder plugin when it has + * successfully decoded a tag. + * + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param tag the tag to send + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag); + +static inline DecoderCommand +decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag) +{ + return decoder_tag(decoder, &is, std::move(tag)); +} + +/** + * Set replay gain values for the following chunks. + * + * @param decoder the decoder object + * @param rgi the replay_gain_info object; may be nullptr to invalidate + * the previous replay gain values + */ +void +decoder_replay_gain(Decoder &decoder, + const ReplayGainInfo *replay_gain_info); + +/** + * Store MixRamp tags. + * + * @param decoder the decoder object + * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate + * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate + */ +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp); + +#endif diff --git a/src/decoder/DecoderBuffer.cxx b/src/decoder/DecoderBuffer.cxx new file mode 100644 index 000000000..a8958d6fd --- /dev/null +++ b/src/decoder/DecoderBuffer.cxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderBuffer.hxx" +#include "DecoderAPI.hxx" + +#include <assert.h> + +bool +DecoderBuffer::Fill() +{ + auto w = buffer.Write(); + if (w.IsEmpty()) + /* buffer is full */ + return false; + + size_t nbytes = decoder_read(decoder, is, + w.data, w.size); + if (nbytes == 0) + /* end of file, I/O error or decoder command + received */ + return false; + + buffer.Append(nbytes); + return true; +} + +ConstBuffer<void> +DecoderBuffer::Need(size_t min_size) +{ + while (true) { + const auto r = Read(); + if (r.size >= min_size) + return r; + + if (!Fill()) + return nullptr; + } +} + +bool +DecoderBuffer::Skip(size_t nbytes) +{ + const auto r = buffer.Read(); + if (r.size >= nbytes) { + buffer.Consume(nbytes); + return true; + } + + buffer.Clear(); + nbytes -= r.size; + + return decoder_skip(decoder, is, nbytes); +} diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx new file mode 100644 index 000000000..9cf47d915 --- /dev/null +++ b/src/decoder/DecoderBuffer.hxx @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2003-2014 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_BUFFER_HXX +#define MPD_DECODER_BUFFER_HXX + +#include "Compiler.h" +#include "util/DynamicFifoBuffer.hxx" +#include "util/ConstBuffer.hxx" + +#include <stddef.h> + +struct Decoder; +class InputStream; + +/** + * This objects handles buffered reads in decoder plugins easily. You + * create a buffer object, and use its high-level methods to fill and + * read it. It will automatically handle shifting the buffer. + */ +class DecoderBuffer { + Decoder *const decoder; + InputStream &is; + + DynamicFifoBuffer<uint8_t> buffer; + +public: + /** + * Creates a new buffer. + * + * @param _decoder the decoder object, used for decoder_read(), + * may be nullptr + * @param _is the input stream object where we should read from + * @param _size the maximum size of the buffer + */ + DecoderBuffer(Decoder *_decoder, InputStream &_is, + size_t _size) + :decoder(_decoder), is(_is), buffer(_size) {} + + const InputStream &GetStream() const { + return is; + } + + void Clear() { + buffer.Clear(); + } + + /** + * Read data from the #InputStream and append it to the buffer. + * + * @return true if data was appended; false if there is no + * data available (yet), end of file, I/O error or a decoder + * command was received + */ + bool Fill(); + + /** + * How many bytes are stored in the buffer? + */ + gcc_pure + size_t GetAvailable() const { + return buffer.GetAvailable(); + } + + /** + * Reads data from the buffer. This data is not yet consumed, + * you have to call Consume() to do that. The returned buffer + * becomes invalid after a Fill() or a Consume() call. + */ + ConstBuffer<void> Read() const { + auto r = buffer.Read(); + return { r.data, r.size }; + } + + /** + * Wait until this number of bytes are available. Returns nullptr on + * error. + */ + ConstBuffer<void> Need(size_t min_size); + + /** + * Consume (delete, invalidate) a part of the buffer. The + * "nbytes" parameter must not be larger than the length + * returned by Read(). + * + * @param nbytes the number of bytes to consume + */ + void Consume(size_t nbytes) { + buffer.Consume(nbytes); + } + + /** + * Skips the specified number of bytes, discarding its data. + * + * @param nbytes the number of bytes to skip + * @return true on success, false on error + */ + bool Skip(size_t nbytes); +}; + +#endif diff --git a/src/decoder/DecoderCommand.hxx b/src/decoder/DecoderCommand.hxx new file mode 100644 index 000000000..a00519644 --- /dev/null +++ b/src/decoder/DecoderCommand.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2014 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_COMMAND_HXX +#define MPD_DECODER_COMMAND_HXX + +#include <stdint.h> + +enum class DecoderCommand : uint8_t { + NONE = 0, + START, + STOP, + SEEK +}; + +#endif diff --git a/src/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx new file mode 100644 index 000000000..c30da6214 --- /dev/null +++ b/src/decoder/DecoderControl.cxx @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "DetachedSong.hxx" + +#include <assert.h> + +DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond) + :mutex(_mutex), client_cond(_client_cond), + state(DecoderState::STOP), + command(DecoderCommand::NONE), + client_is_waiting(false), + song(nullptr), + replay_gain_db(0), replay_gain_prev_db(0) {} + +DecoderControl::~DecoderControl() +{ + ClearError(); + + delete song; +} + +void +DecoderControl::WaitForDecoder() +{ + assert(!client_is_waiting); + client_is_waiting = true; + + client_cond.wait(mutex); + + assert(client_is_waiting); + client_is_waiting = false; +} + +bool +DecoderControl::IsCurrentSong(const DetachedSong &_song) const +{ + switch (state) { + case DecoderState::STOP: + case DecoderState::ERROR: + return false; + + case DecoderState::START: + case DecoderState::DECODE: + return song->IsSame(_song); + } + + assert(false); + gcc_unreachable(); +} + +void +DecoderControl::Start(DetachedSong *_song, + SongTime _start_time, SongTime _end_time, + MusicBuffer &_buffer, MusicPipe &_pipe) +{ + assert(_song != nullptr); + assert(_pipe.IsEmpty()); + + delete song; + song = _song; + start_time = _start_time; + end_time = _end_time; + buffer = &_buffer; + pipe = &_pipe; + + LockSynchronousCommand(DecoderCommand::START); +} + +void +DecoderControl::Stop() +{ + Lock(); + + if (command != DecoderCommand::NONE) + /* Attempt to cancel the current command. If it's too + late and the decoder thread is already executing + the old command, we'll call STOP again in this + function (see below). */ + SynchronousCommandLocked(DecoderCommand::STOP); + + if (state != DecoderState::STOP && state != DecoderState::ERROR) + SynchronousCommandLocked(DecoderCommand::STOP); + + Unlock(); +} + +bool +DecoderControl::Seek(SongTime t) +{ + assert(state != DecoderState::START); + + if (state == DecoderState::STOP || + state == DecoderState::ERROR || !seekable) + return false; + + seek_time = t; + seek_error = false; + LockSynchronousCommand(DecoderCommand::SEEK); + + return !seek_error; +} + +void +DecoderControl::Quit() +{ + assert(thread.IsDefined()); + + quit = true; + LockAsynchronousCommand(DecoderCommand::STOP); + + thread.Join(); +} + +void +DecoderControl::CycleMixRamp() +{ + previous_mix_ramp = std::move(mix_ramp); + mix_ramp.Clear(); +} diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx new file mode 100644 index 000000000..ed2b8c538 --- /dev/null +++ b/src/decoder/DecoderControl.hxx @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2003-2014 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_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX + +#include "DecoderCommand.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "Chrono.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <stdint.h> + +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif + +class DetachedSong; +class MusicBuffer; +class MusicPipe; + +enum class DecoderState : uint8_t { + STOP = 0, + START, + DECODE, + + /** + * The last "START" command failed, because there was an I/O + * error or because no decoder was able to decode the file. + * This state will only come after START; once the state has + * turned to DECODE, by definition no such error can occur. + */ + ERROR, +}; + +struct DecoderControl { + /** + * The handle of the decoder thread. + */ + Thread thread; + + /** + * This lock protects #state and #command. + * + * This is usually a reference to PlayerControl::mutex, so + * that both player thread and decoder thread share a mutex. + * This simplifies synchronization with #cond and + * #client_cond. + */ + Mutex &mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + Cond cond; + + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + * + * This is usually a reference to PlayerControl::cond. + */ + Cond &client_cond; + + DecoderState state; + DecoderCommand command; + + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DecoderState::ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DecoderState::START). + */ + Error error; + + bool quit; + + /** + * Is the client currently waiting for the DecoderThread? If + * false, the DecoderThread may omit invoking Cond::signal(), + * reducing the number of system calls. + */ + bool client_is_waiting; + + bool seek_error; + bool seekable; + SongTime seek_time; + + /** the format of the song file */ + AudioFormat in_audio_format; + + /** the format being sent to the music pipe */ + AudioFormat out_audio_format; + + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DecoderCommand::START + * command. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + DetachedSong *song; + + /** + * The initial seek position, e.g. to the start of a sub-track + * described by a CUE file. + * + * This attribute is set by Start(). + */ + SongTime start_time; + + /** + * The decoder will stop when it reaches this position. 0 + * means don't stop before the end of the file. + * + * This attribute is set by Start(). + */ + SongTime end_time; + + SignedSongTime total_time; + + /** the #MusicChunk allocator */ + MusicBuffer *buffer; + + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ + MusicPipe *pipe; + + float replay_gain_db; + float replay_gain_prev_db; + + MixRampInfo mix_ramp, previous_mix_ramp; + + /** + * @param _mutex see #mutex + * @param _client_cond see #client_cond + */ + DecoderControl(Mutex &_mutex, Cond &_client_cond); + ~DecoderControl(); + + /** + * Locks the object. + */ + void Lock() const { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const { + mutex.unlock(); + } + + /** + * Signals the object. This function is only valid in the + * player thread. The object should be locked prior to + * calling this function. + */ + void Signal() { + cond.signal(); + } + + /** + * Waits for a signal on the #DecoderControl object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ + void Wait() { + cond.wait(mutex); + } + + /** + * Waits for a signal from the decoder thread. This object + * must be locked prior to calling this function. This method + * is only valid in the player thread. + * + * Caller must hold the lock. + */ + void WaitForDecoder(); + + bool IsIdle() const { + return state == DecoderState::STOP || + state == DecoderState::ERROR; + } + + gcc_pure + bool LockIsIdle() const { + Lock(); + bool result = IsIdle(); + Unlock(); + return result; + } + + bool IsStarting() const { + return state == DecoderState::START; + } + + gcc_pure + bool LockIsStarting() const { + Lock(); + bool result = IsStarting(); + Unlock(); + return result; + } + + bool HasFailed() const { + assert(command == DecoderCommand::NONE); + + return state == DecoderState::ERROR; + } + + gcc_pure + bool LockHasFailed() const { + Lock(); + bool result = HasFailed(); + Unlock(); + return result; + } + + /** + * Checks whether an error has occurred, and if so, returns a + * copy of the #Error object. + * + * Caller must lock the object. + */ + gcc_pure + Error GetError() const { + assert(command == DecoderCommand::NONE); + assert(state != DecoderState::ERROR || error.IsDefined()); + + Error result; + if (state == DecoderState::ERROR) + result.Set(error); + return result; + } + + /** + * Like GetError(), but locks and unlocks the object. + */ + gcc_pure + Error LockGetError() const { + Lock(); + Error result = GetError(); + Unlock(); + return result; + } + + /** + * Clear the error condition and free the #Error object (if any). + * + * Caller must lock the object. + */ + void ClearError() { + if (state == DecoderState::ERROR) { + error.Clear(); + state = DecoderState::STOP; + } + } + + /** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ + gcc_pure + bool IsCurrentSong(const DetachedSong &_song) const; + + gcc_pure + bool LockIsCurrentSong(const DetachedSong &_song) const { + Lock(); + const bool result = IsCurrentSong(_song); + Unlock(); + return result; + } + +private: + /** + * Wait for the command to be finished by the decoder thread. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void WaitCommandLocked() { + while (command != DecoderCommand::NONE) + WaitForDecoder(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void SynchronousCommandLocked(DecoderCommand cmd) { + command = cmd; + Signal(); + WaitCommandLocked(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. This method locks the + * object. + */ + void LockSynchronousCommand(DecoderCommand cmd) { + Lock(); + ClearError(); + SynchronousCommandLocked(cmd); + Unlock(); + } + + void LockAsynchronousCommand(DecoderCommand cmd) { + Lock(); + command = cmd; + Signal(); + Unlock(); + } + +public: + /** + * Start the decoder. + * + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder + * @param start_time see #DecoderControl + * @param end_time see #DecoderControl + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ + void Start(DetachedSong *song, SongTime start_time, SongTime end_time, + MusicBuffer &buffer, MusicPipe &pipe); + + void Stop(); + + bool Seek(SongTime t); + + void Quit(); + + const char *GetMixRampStart() const { + return mix_ramp.GetStart(); + } + + const char *GetMixRampEnd() const { + return mix_ramp.GetEnd(); + } + + const char *GetMixRampPreviousEnd() const { + return previous_mix_ramp.GetEnd(); + } + + void SetMixRamp(MixRampInfo &&new_value) { + mix_ramp = std::move(new_value); + } + + /** + * Move mixramp_end to mixramp_prev_end and clear + * mixramp_start/mixramp_end. + */ + void CycleMixRamp(); +}; + +#endif diff --git a/src/decoder/DecoderError.cxx b/src/decoder/DecoderError.cxx new file mode 100644 index 000000000..bd3842837 --- /dev/null +++ b/src/decoder/DecoderError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderError.hxx" +#include "util/Domain.hxx" + +const Domain decoder_domain("decoder"); diff --git a/src/decoder/DecoderError.hxx b/src/decoder/DecoderError.hxx new file mode 100644 index 000000000..83cf98204 --- /dev/null +++ b/src/decoder/DecoderError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_ERROR_HXX +#define MPD_DECODER_ERROR_HXX + +extern const class Domain decoder_domain; + +#endif diff --git a/src/decoder/DecoderInternal.cxx b/src/decoder/DecoderInternal.cxx new file mode 100644 index 000000000..f35878682 --- /dev/null +++ b/src/decoder/DecoderInternal.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderInternal.hxx" +#include "DecoderControl.hxx" +#include "pcm/PcmConvert.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> + +Decoder::~Decoder() +{ + /* caller must flush the chunk */ + assert(chunk == nullptr); + + if (convert != nullptr) { + convert->Close(); + delete convert; + } + + delete song_tag; + delete stream_tag; + delete decoder_tag; +} + +/** + * All chunks are full of decoded data; wait for the player to free + * one. + */ +static DecoderCommand +need_chunks(DecoderControl &dc) +{ + if (dc.command == DecoderCommand::NONE) + dc.Wait(); + + return dc.command; +} + +MusicChunk * +Decoder::GetChunk() +{ + DecoderCommand cmd; + + if (chunk != nullptr) + return chunk; + + do { + chunk = dc.buffer->Allocate(); + if (chunk != nullptr) { + chunk->replay_gain_serial = replay_gain_serial; + if (replay_gain_serial != 0) + chunk->replay_gain_info = replay_gain_info; + + return chunk; + } + + dc.Lock(); + cmd = need_chunks(dc); + dc.Unlock(); + } while (cmd == DecoderCommand::NONE); + + return nullptr; +} + +void +Decoder::FlushChunk() +{ + assert(!seeking); + assert(!initial_seek_running); + assert(!initial_seek_pending); + assert(chunk != nullptr); + + if (chunk->IsEmpty()) + dc.buffer->Return(chunk); + else + dc.pipe->Push(chunk); + + chunk = nullptr; + + dc.Lock(); + if (dc.client_is_waiting) + dc.client_cond.signal(); + dc.Unlock(); +} diff --git a/src/decoder/DecoderInternal.hxx b/src/decoder/DecoderInternal.hxx new file mode 100644 index 000000000..24b665e85 --- /dev/null +++ b/src/decoder/DecoderInternal.hxx @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2003-2014 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_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX + +#include "ReplayGainInfo.hxx" +#include "util/Error.hxx" + +class PcmConvert; +struct MusicChunk; +struct DecoderControl; +struct Tag; + +struct Decoder { + DecoderControl &dc; + + /** + * For converting input data to the configured audio format. + * nullptr means no conversion necessary. + */ + PcmConvert *convert; + + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + + /** + * Is the initial seek (to the start position of the sub-song) + * pending, or has it been performed already? + */ + bool initial_seek_pending; + + /** + * Is the initial seek currently running? During this time, + * the decoder command is SEEK. This flag is set by + * decoder_get_virtual_command(), when the virtual SEEK + * command is generated for the first time. + */ + bool initial_seek_running; + + /** + * This flag is set by decoder_seek_time(), and checked by + * decoder_command_finished(). It is used to clean up after + * seeking. + */ + bool seeking; + + /** + * The tag from the song object. This is only used for local + * files, because we expect the stream server to send us a new + * tag each time we play it. + */ + Tag *song_tag; + + /** the last tag received from the stream */ + Tag *stream_tag; + + /** the last tag received from the decoder plugin */ + Tag *decoder_tag; + + /** the chunk currently being written to */ + MusicChunk *chunk; + + ReplayGainInfo replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; + + /** + * An error has occurred (in DecoderAPI.cxx), and the plugin + * will be asked to stop. + */ + Error error; + + Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag) + :dc(_dc), + convert(nullptr), + timestamp(0), + initial_seek_pending(_initial_seek_pending), + initial_seek_running(false), + seeking(false), + song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr), + chunk(nullptr), + replay_gain_serial(0) { + } + + ~Decoder(); + + /** + * Returns the current chunk the decoder writes to, or allocates a new + * chunk if there is none. + * + * @return the chunk, or NULL if we have received a decoder command + */ + MusicChunk *GetChunk(); + + /** + * Flushes the current chunk. + * + * Caller must not lock the #DecoderControl object. + */ + void FlushChunk(); +}; + +#endif diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx new file mode 100644 index 000000000..cd6881ce2 --- /dev/null +++ b/src/decoder/DecoderList.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "plugins/AudiofileDecoderPlugin.hxx" +#include "plugins/PcmDecoderPlugin.hxx" +#include "plugins/DsdiffDecoderPlugin.hxx" +#include "plugins/DsfDecoderPlugin.hxx" +#include "plugins/FlacDecoderPlugin.h" +#include "plugins/OpusDecoderPlugin.h" +#include "plugins/VorbisDecoderPlugin.h" +#include "plugins/AdPlugDecoderPlugin.h" +#include "plugins/WavpackDecoderPlugin.hxx" +#include "plugins/FfmpegDecoderPlugin.hxx" +#include "plugins/GmeDecoderPlugin.hxx" +#include "plugins/FaadDecoderPlugin.hxx" +#include "plugins/MadDecoderPlugin.hxx" +#include "plugins/SndfileDecoderPlugin.hxx" +#include "plugins/Mpg123DecoderPlugin.hxx" +#include "plugins/WildmidiDecoderPlugin.hxx" +#include "plugins/MikmodDecoderPlugin.hxx" +#include "plugins/ModplugDecoderPlugin.hxx" +#include "plugins/MpcdecDecoderPlugin.hxx" +#include "plugins/FluidsynthDecoderPlugin.hxx" +#include "plugins/SidplayDecoderPlugin.hxx" +#include "util/Macros.hxx" + +#include <string.h> + +const struct DecoderPlugin *const decoder_plugins[] = { +#ifdef HAVE_MAD + &mad_decoder_plugin, +#endif +#ifdef HAVE_MPG123 + &mpg123_decoder_plugin, +#endif +#ifdef ENABLE_VORBIS_DECODER + &vorbis_decoder_plugin, +#endif +#if defined(HAVE_FLAC) + &oggflac_decoder_plugin, +#endif +#ifdef HAVE_FLAC + &flac_decoder_plugin, +#endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif +#ifdef ENABLE_SNDFILE + &sndfile_decoder_plugin, +#endif +#ifdef HAVE_AUDIOFILE + &audiofile_decoder_plugin, +#endif +#ifdef ENABLE_DSD + &dsdiff_decoder_plugin, + &dsf_decoder_plugin, +#endif +#ifdef HAVE_FAAD + &faad_decoder_plugin, +#endif +#ifdef HAVE_MPCDEC + &mpcdec_decoder_plugin, +#endif +#ifdef HAVE_WAVPACK + &wavpack_decoder_plugin, +#endif +#ifdef HAVE_MODPLUG + &modplug_decoder_plugin, +#endif +#ifdef ENABLE_MIKMOD_DECODER + &mikmod_decoder_plugin, +#endif +#ifdef ENABLE_SIDPLAY + &sidplay_decoder_plugin, +#endif +#ifdef ENABLE_WILDMIDI + &wildmidi_decoder_plugin, +#endif +#ifdef ENABLE_FLUIDSYNTH + &fluidsynth_decoder_plugin, +#endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif +#ifdef HAVE_FFMPEG + &ffmpeg_decoder_plugin, +#endif +#ifdef HAVE_GME + &gme_decoder_plugin, +#endif + &pcm_decoder_plugin, + nullptr +}; + +static constexpr unsigned num_decoder_plugins = + ARRAY_SIZE(decoder_plugins) - 1; + +/** which plugins have been initialized successfully? */ +bool decoder_plugins_enabled[num_decoder_plugins]; + +const struct DecoderPlugin * +decoder_plugin_from_name(const char *name) +{ + return decoder_plugins_find([=](const DecoderPlugin &plugin){ + return strcmp(plugin.name, name) == 0; + }); +} + +void decoder_plugin_init_all(void) +{ + struct config_param empty; + + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) { + const DecoderPlugin &plugin = *decoder_plugins[i]; + const struct config_param *param = + config_find_block(CONF_DECODER, "plugin", plugin.name); + + if (param == nullptr) + param = ∅ + else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin.Init(*param)) + decoder_plugins_enabled[i] = true; + } +} + +void decoder_plugin_deinit_all(void) +{ + decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){ + plugin.Finish(); + }); +} + +bool +decoder_plugins_supports_suffix(const char *suffix) +{ + return decoder_plugins_try([suffix](const DecoderPlugin &plugin){ + return plugin.SupportsSuffix(suffix); + }); +} diff --git a/src/decoder/DecoderList.hxx b/src/decoder/DecoderList.hxx new file mode 100644 index 000000000..47085d4ae --- /dev/null +++ b/src/decoder/DecoderList.hxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 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_LIST_HXX +#define MPD_DECODER_LIST_HXX + +#include "Compiler.h" + +struct DecoderPlugin; + +extern const struct DecoderPlugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + +/* interface for using plugins */ + +gcc_pure +const struct DecoderPlugin * +decoder_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void decoder_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void decoder_plugin_deinit_all(void); + +template<typename F> +static inline const DecoderPlugin * +decoder_plugins_find(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return decoder_plugins[i]; + + return nullptr; +} + +template<typename F> +static inline bool +decoder_plugins_try(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return true; + + return false; +} + +template<typename F> +static inline void +decoder_plugins_for_each(F f) +{ + for (auto i = decoder_plugins; *i != nullptr; ++i) + f(**i); +} + +template<typename F> +static inline void +decoder_plugins_for_each_enabled(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i]) + f(*decoder_plugins[i]); +} + +/** + * Is there at least once #DecoderPlugin that supports the specified + * file name suffix? + */ +gcc_pure gcc_nonnull_all +bool +decoder_plugins_supports_suffix(const char *suffix); + +#endif diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx new file mode 100644 index 000000000..3be812c3b --- /dev/null +++ b/src/decoder/DecoderPlugin.cxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderPlugin.hxx" +#include "util/StringUtil.hxx" + +#include <assert.h> + +bool +DecoderPlugin::SupportsSuffix(const char *suffix) const +{ + assert(suffix != nullptr); + + return suffixes != nullptr && string_array_contains(suffixes, suffix); + +} + +bool +DecoderPlugin::SupportsMimeType(const char *mime_type) const +{ + assert(mime_type != nullptr); + + return mime_types != nullptr && + string_array_contains(mime_types, mime_type); +} diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx new file mode 100644 index 000000000..dbf3db9aa --- /dev/null +++ b/src/decoder/DecoderPlugin.hxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2014 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_PLUGIN_HXX +#define MPD_DECODER_PLUGIN_HXX + +#include "Compiler.h" + +struct config_param; +class InputStream; +struct tag_handler; +class Path; + +/** + * Opaque handle which the decoder plugin passes to the functions in + * this header. + */ +struct Decoder; + +struct DecoderPlugin { + const char *name; + + /** + * Initialize the decoder plugin. Optional method. + * + * @param param a configuration block for this plugin, or nullptr + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const config_param ¶m); + + /** + * Deinitialize a decoder plugin which was initialized + * successfully. Optional method. + */ + void (*finish)(void); + + /** + * Decode a stream (data read from an #input_stream object). + * + * Either implement this method or file_decode(). If + * possible, it is recommended to implement this method, + * because it is more versatile. + */ + void (*stream_decode)(Decoder &decoder, InputStream &is); + + /** + * Decode a local file. + * + * Either implement this method or stream_decode(). + */ + void (*file_decode)(Decoder &decoder, Path path_fs); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_file)(Path path_fs, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_stream)(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * @brief Return a "virtual" filename for subtracks in + * container formats like flac + * @param const char* pathname full pathname for the file on fs + * @param const unsigned int tnum track number + * + * @return nullptr if there are no multiple files + * a filename for every single track according to tnum (param 2) + * do not include full pathname here, just the "virtual" file + * + * Free the return value with delete[]. + */ + char* (*container_scan)(Path path_fs, const unsigned int tnum); + + /* last element in these arrays must always be a nullptr: */ + const char *const*suffixes; + const char *const*mime_types; + + /** + * Initialize a decoder plugin. + * + * @param param a configuration block for this plugin, or nullptr if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ + bool Init(const config_param ¶m) const { + return init != nullptr + ? init(param) + : true; + } + + /** + * Deinitialize a decoder plugin which was initialized successfully. + */ + void Finish() const { + if (finish != nullptr) + finish(); + } + + /** + * Decode a stream. + */ + void StreamDecode(Decoder &decoder, InputStream &is) const { + stream_decode(decoder, is); + } + + /** + * Decode a file. + */ + template<typename P> + void FileDecode(Decoder &decoder, P path_fs) const { + file_decode(decoder, path_fs); + } + + /** + * Read the tag of a file. + */ + template<typename P> + bool ScanFile(P path_fs, + const tag_handler &handler, void *handler_ctx) const { + return scan_file != nullptr + ? scan_file(path_fs, &handler, handler_ctx) + : false; + } + + /** + * Read the tag of a stream. + */ + bool ScanStream(InputStream &is, + const tag_handler &handler, void *handler_ctx) const { + return scan_stream != nullptr + ? scan_stream(is, &handler, handler_ctx) + : false; + } + + /** + * return "virtual" tracks in a container + */ + template<typename P> + char *ContainerScan(P path, const unsigned int tnum) const { + return container_scan(path, tnum); + } + + /** + * Does the plugin announce the specified file name suffix? + */ + gcc_pure gcc_nonnull_all + bool SupportsSuffix(const char *suffix) const; + + /** + * Does the plugin announce the specified MIME type? + */ + gcc_pure gcc_nonnull_all + bool SupportsMimeType(const char *mime_type) const; +}; + +#endif diff --git a/src/decoder/DecoderPrint.cxx b/src/decoder/DecoderPrint.cxx new file mode 100644 index 000000000..54b89c36c --- /dev/null +++ b/src/decoder/DecoderPrint.cxx @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderPrint.hxx" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "client/Client.hxx" + +#include <functional> + +#include <assert.h> + +static void +decoder_plugin_print(Client &client, + const DecoderPlugin &plugin) +{ + const char *const*p; + + assert(plugin.name != nullptr); + + client_printf(client, "plugin: %s\n", plugin.name); + + if (plugin.suffixes != nullptr) + for (p = plugin.suffixes; *p != nullptr; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin.mime_types != nullptr) + for (p = plugin.mime_types; *p != nullptr; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(Client &client) +{ + using namespace std::placeholders; + const auto f = std::bind(decoder_plugin_print, std::ref(client), _1); + decoder_plugins_for_each_enabled(f); +} diff --git a/src/decoder/DecoderPrint.hxx b/src/decoder/DecoderPrint.hxx new file mode 100644 index 000000000..695bd099d --- /dev/null +++ b/src/decoder/DecoderPrint.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 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_PRINT_HXX +#define MPD_DECODER_PRINT_HXX + +class Client; + +void +decoder_list_print(Client &client); + +#endif diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx new file mode 100644 index 000000000..dd5518b98 --- /dev/null +++ b/src/decoder/DecoderThread.cxx @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2003-2014 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 "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DecoderError.hxx" +#include "DecoderPlugin.hxx" +#include "DetachedSong.hxx" +#include "system/FatalError.hxx" +#include "MusicPipe.hxx" +#include "fs/Traits.hxx" +#include "fs/AllocatedPath.hxx" +#include "DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "input/LocalOpen.hxx" +#include "DecoderList.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Name.hxx" +#include "tag/ApeReplayGain.hxx" +#include "Log.hxx" + +#include <functional> + +static constexpr Domain decoder_thread_domain("decoder_thread"); + +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #DecoderControl object; must be locked + */ +static void +decoder_command_finished_locked(DecoderControl &dc) +{ + assert(dc.command != DecoderCommand::NONE); + + dc.command = DecoderCommand::NONE; + + dc.client_cond.signal(); +} + +/** + * Opens the input stream with input_stream::Open(), and waits until + * the stream gets ready. If a decoder STOP command is received + * during that, it cancels the operation (but does not close the + * stream). + * + * Unlock the decoder before calling this function. + * + * @return an input_stream on success or if #DecoderCommand::STOP is + * received, nullptr on error + */ +static InputStream * +decoder_input_stream_open(DecoderControl &dc, const char *uri) +{ + Error error; + + InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error); + if (is == nullptr) { + if (error.IsDefined()) + LogError(error); + + return nullptr; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + dc.Lock(); + + is->Update(); + while (!is->IsReady() && + dc.command != DecoderCommand::STOP) { + dc.Wait(); + + is->Update(); + } + + if (!is->Check(error)) { + dc.Unlock(); + + LogError(error); + return nullptr; + } + + dc.Unlock(); + + return is; +} + +static InputStream * +decoder_input_stream_open(DecoderControl &dc, Path path) +{ + Error error; + + InputStream *is = OpenLocalInputStream(path, dc.mutex, dc.cond, error); + if (is == nullptr) { + LogError(error); + return nullptr; + } + + assert(is->IsReady()); + + return is; +} + +static bool +decoder_stream_decode(const DecoderPlugin &plugin, + Decoder &decoder, + InputStream &input_stream) +{ + assert(plugin.stream_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(input_stream.IsReady()); + assert(decoder.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (decoder.dc.command == DecoderCommand::STOP) + return true; + + /* rewind the stream, so each plugin gets a fresh start */ + input_stream.Rewind(IgnoreError()); + + decoder.dc.Unlock(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.StreamDecode(decoder, input_stream); + + SetThreadName("decoder"); + + decoder.dc.Lock(); + + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + return decoder.dc.state != DecoderState::START; +} + +static bool +decoder_file_decode(const DecoderPlugin &plugin, + Decoder &decoder, Path path) +{ + assert(plugin.file_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(!path.IsNull()); + assert(path.IsAbsolute()); + assert(decoder.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (decoder.dc.command == DecoderCommand::STOP) + return true; + + decoder.dc.Unlock(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.FileDecode(decoder, path); + + SetThreadName("decoder"); + + decoder.dc.Lock(); + + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + return decoder.dc.state != DecoderState::START; +} + +gcc_pure +static bool +decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is) +{ + assert(plugin.stream_decode != nullptr); + + const char *mime_type = is.GetMimeType(); + return mime_type != nullptr && plugin.SupportsMimeType(mime_type); +} + +gcc_pure +static bool +decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix) +{ + assert(plugin.stream_decode != nullptr); + + return suffix != nullptr && plugin.SupportsSuffix(suffix); +} + +gcc_pure +static bool +decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, + const char *suffix) +{ + return plugin.stream_decode != nullptr && + (decoder_check_plugin_mime(plugin, is) || + decoder_check_plugin_suffix(plugin, suffix)); +} + +static bool +decoder_run_stream_plugin(Decoder &decoder, InputStream &is, + const char *suffix, + const DecoderPlugin &plugin, + bool &tried_r) +{ + if (!decoder_check_plugin(plugin, is, suffix)) + return false; + + tried_r = true; + return decoder_stream_decode(plugin, decoder, is); +} + +static bool +decoder_run_stream_locked(Decoder &decoder, InputStream &is, + const char *uri, bool &tried_r) +{ + UriSuffixBuffer suffix_buffer; + const char *const suffix = uri_get_suffix(uri, suffix_buffer); + + using namespace std::placeholders; + const auto f = std::bind(decoder_run_stream_plugin, + std::ref(decoder), std::ref(is), suffix, + _1, std::ref(tried_r)); + return decoder_plugins_try(f); +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(Decoder &decoder, InputStream &is) +{ + const struct DecoderPlugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != nullptr && plugin->stream_decode != nullptr && + decoder_stream_decode(*plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(Decoder &decoder, const char *uri) +{ + DecoderControl &dc = decoder.dc; + InputStream *input_stream; + bool success; + + dc.Unlock(); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == nullptr) { + dc.Lock(); + return false; + } + + dc.Lock(); + + bool tried = false; + success = dc.command == DecoderCommand::STOP || + decoder_run_stream_locked(decoder, *input_stream, uri, + tried) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + (!tried && + decoder_run_stream_fallback(decoder, *input_stream)); + + dc.Unlock(); + delete input_stream; + dc.Lock(); + + return success; +} + +/** + * Attempt to load replay gain data, and pass it to + * decoder_replay_gain(). + */ +static void +decoder_load_replay_gain(Decoder &decoder, Path path_fs) +{ + ReplayGainInfo info; + if (replay_gain_ape_read(path_fs, info)) + decoder_replay_gain(decoder, &info); +} + +static bool +TryDecoderFile(Decoder &decoder, Path path_fs, const char *suffix, + const DecoderPlugin &plugin) +{ + if (!plugin.SupportsSuffix(suffix)) + return false; + + DecoderControl &dc = decoder.dc; + + if (plugin.file_decode != nullptr) { + dc.Lock(); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + dc.Unlock(); + } else if (plugin.stream_decode != nullptr) { + InputStream *input_stream = + decoder_input_stream_open(dc, path_fs); + if (input_stream == nullptr) + return false; + + dc.Lock(); + + bool success = decoder_stream_decode(plugin, decoder, + *input_stream); + + dc.Unlock(); + + delete input_stream; + + if (success) { + dc.Lock(); + return true; + } + } + + return false; +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(Decoder &decoder, const char *uri_utf8, Path path_fs) +{ + const char *suffix = uri_get_suffix(uri_utf8); + if (suffix == nullptr) + return false; + + DecoderControl &dc = decoder.dc; + dc.Unlock(); + + decoder_load_replay_gain(decoder, path_fs); + + if (decoder_plugins_try([&decoder, path_fs, + suffix](const DecoderPlugin &plugin){ + return TryDecoderFile(decoder, + path_fs, suffix, + plugin); + })) + return true; + + dc.Lock(); + return false; +} + +static void +decoder_run_song(DecoderControl &dc, + const DetachedSong &song, const char *uri, Path path_fs) +{ + Decoder decoder(dc, dc.start_time.IsPositive(), + new Tag(song.GetTag())); + int ret; + + dc.state = DecoderState::START; + + decoder_command_finished_locked(dc); + + ret = !path_fs.IsNull() + ? decoder_run_file(decoder, uri, path_fs) + : decoder_run_stream(decoder, uri); + + dc.Unlock(); + + /* flush the last chunk */ + + if (decoder.chunk != nullptr) + decoder.FlushChunk(); + + dc.Lock(); + + if (decoder.error.IsDefined()) { + /* copy the Error from sruct Decoder to + DecoderControl */ + dc.state = DecoderState::ERROR; + dc.error = std::move(decoder.error); + } else if (ret) + dc.state = DecoderState::STOP; + else { + dc.state = DecoderState::ERROR; + + const char *error_uri = song.GetURI(); + const std::string allocated = uri_remove_auth(error_uri); + if (!allocated.empty()) + error_uri = allocated.c_str(); + + dc.error.Format(decoder_domain, + "Failed to decode %s", error_uri); + } + + dc.client_cond.signal(); +} + +static void +decoder_run(DecoderControl &dc) +{ + dc.ClearError(); + + assert(dc.song != nullptr); + const DetachedSong &song = *dc.song; + + const char *const uri_utf8 = song.GetRealURI(); + + Path path_fs = Path::Null(); + AllocatedPath path_buffer = AllocatedPath::Null(); + if (PathTraitsUTF8::IsAbsolute(uri_utf8)) { + path_buffer = AllocatedPath::FromUTF8(uri_utf8, dc.error); + if (path_buffer.IsNull()) { + dc.state = DecoderState::ERROR; + decoder_command_finished_locked(dc); + return; + } + + path_fs = path_buffer; + } + + decoder_run_song(dc, song, uri_utf8, path_fs); + +} + +static void +decoder_task(void *arg) +{ + DecoderControl &dc = *(DecoderControl *)arg; + + SetThreadName("decoder"); + + dc.Lock(); + + do { + assert(dc.state == DecoderState::STOP || + dc.state == DecoderState::ERROR); + + switch (dc.command) { + case DecoderCommand::START: + dc.CycleMixRamp(); + dc.replay_gain_prev_db = dc.replay_gain_db; + dc.replay_gain_db = 0; + + decoder_run(dc); + break; + + case DecoderCommand::SEEK: + /* this seek was too late, and the decoder had + already finished; start a new decoder */ + + /* we need to clear the pipe here; usually the + PlayerThread is responsible, but it is not + aware that the decoder has finished */ + dc.pipe->Clear(*dc.buffer); + + decoder_run(dc); + break; + + case DecoderCommand::STOP: + decoder_command_finished_locked(dc); + break; + + case DecoderCommand::NONE: + dc.Wait(); + break; + } + } while (dc.command != DecoderCommand::NONE || !dc.quit); + + dc.Unlock(); +} + +void +decoder_thread_start(DecoderControl &dc) +{ + assert(!dc.thread.IsDefined()); + + dc.quit = false; + + Error error; + if (!dc.thread.Start(decoder_task, &dc, error)) + FatalError(error); +} diff --git a/src/decoder/DecoderThread.hxx b/src/decoder/DecoderThread.hxx new file mode 100644 index 000000000..d5fde281c --- /dev/null +++ b/src/decoder/DecoderThread.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 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_THREAD_HXX +#define MPD_DECODER_THREAD_HXX + +struct DecoderControl; + +void +decoder_thread_start(DecoderControl &dc); + +#endif diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx deleted file mode 100644 index eafedda8f..000000000 --- a/src/decoder/DsdLib.cxx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2003-2013 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. - */ - -/* \file - * - * This file contains functions used by the DSF and DSDIFF decoders. - * - */ - -#include "config.h" -#include "DsdLib.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "util/bit_reverse.h" -#include "tag/TagHandler.hxx" -#include "tag/TagId3.hxx" -#include "util/Error.hxx" - -#include <unistd.h> -#include <string.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -#ifdef HAVE_ID3TAG -#include <id3tag.h> -#endif - -bool -DsdId::Equals(const char *s) const -{ - assert(s != nullptr); - assert(strlen(s) == sizeof(value)); - - return memcmp(value, s, sizeof(value)) == 0; -} - -/** - * Skip the #input_stream to the specified offset. - */ -bool -dsdlib_skip_to(Decoder *decoder, InputStream &is, - int64_t offset) -{ - if (is.IsSeekable()) - return is.Seek(offset, SEEK_SET, IgnoreError()); - - if (is.GetOffset() > offset) - return false; - - char buffer[8192]; - while (is.GetOffset() < offset) { - size_t length = sizeof(buffer); - if (offset - is.GetOffset() < (int64_t)length) - length = offset - is.GetOffset(); - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - } - - assert(is.GetOffset() == offset); - return true; -} - -/** - * Skip some bytes from the #input_stream. - */ -bool -dsdlib_skip(Decoder *decoder, InputStream &is, - int64_t delta) -{ - assert(delta >= 0); - - if (delta == 0) - return true; - - if (is.IsSeekable()) - return is.Seek(delta, SEEK_CUR, IgnoreError()); - - char buffer[8192]; - while (delta > 0) { - size_t length = sizeof(buffer); - if ((int64_t)length > delta) - length = delta; - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - - delta -= nbytes; - } - - return true; -} - -#ifdef HAVE_ID3TAG -void -dsdlib_tag_id3(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, int64_t tagoffset) -{ - assert(tagoffset >= 0); - - if (tagoffset == 0) - return; - - if (!dsdlib_skip_to(nullptr, is, tagoffset)) - return; - - struct id3_tag *id3_tag = nullptr; - id3_length_t count; - - /* Prevent broken files causing problems */ - const auto size = is.GetSize(); - const auto offset = is.GetOffset(); - if (offset >= size) - return; - - count = size - offset; - - /* Check and limit id3 tag size to prevent a stack overflow */ - if (count == 0 || count > 4096) - return; - - id3_byte_t dsdid3[count]; - id3_byte_t *dsdid3data; - dsdid3data = dsdid3; - - if (!decoder_read_full(nullptr, is, dsdid3data, count)) - return; - - id3_tag = id3_tag_parse(dsdid3data, count); - if (id3_tag == nullptr) - return; - - scan_id3_tag(id3_tag, handler, handler_ctx); - - id3_tag_delete(id3_tag); - - return; -} -#endif diff --git a/src/decoder/DsdLib.hxx b/src/decoder/DsdLib.hxx deleted file mode 100644 index 5c6127149..000000000 --- a/src/decoder/DsdLib.hxx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_DSDLIB_HXX -#define MPD_DECODER_DSDLIB_HXX - -#include "system/ByteOrder.hxx" -#include "Compiler.h" - -#include <stddef.h> -#include <stdint.h> - -struct Decoder; -struct InputStream; - -struct DsdId { - char value[4]; - - gcc_pure - bool Equals(const char *s) const; -}; - -class DsdUint64 { - uint32_t lo; - uint32_t hi; - -public: - constexpr uint64_t Read() const { - return (uint64_t(FromLE32(hi)) << 32) | - uint64_t(FromLE32(lo)); - } -}; - -class DffDsdUint64 { - uint32_t hi; - uint32_t lo; - -public: - constexpr uint64_t Read() const { - return (uint64_t(FromBE32(hi)) << 32) | - uint64_t(FromBE32(lo)); - } -}; - -bool -dsdlib_skip_to(Decoder *decoder, InputStream &is, - int64_t offset); - -bool -dsdlib_skip(Decoder *decoder, InputStream &is, - int64_t delta); - -/** - * Add tags from ID3 tag. All tags commonly found in the ID3 tags of - * DSF and DSDIFF files are imported - */ -void -dsdlib_tag_id3(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, int64_t tagoffset); - -#endif diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx deleted file mode 100644 index 767395215..000000000 --- a/src/decoder/DsdiffDecoderPlugin.cxx +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright (C) 2003-2013 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. - */ - -/* \file - * - * This plugin decodes DSDIFF data (SACD) embedded in DFF files. - * The DFF code was modeled after the specification found here: - * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf - * - * All functions common to both DSD decoders have been moved to dsdlib - */ - -#include "config.h" -#include "DsdiffDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "util/bit_reverse.h" -#include "util/Error.hxx" -#include "system/ByteOrder.hxx" -#include "tag/TagHandler.hxx" -#include "DsdLib.hxx" -#include "Log.hxx" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -struct DsdiffHeader { - DsdId id; - DffDsdUint64 size; - DsdId format; -}; - -struct DsdiffChunkHeader { - DsdId id; - DffDsdUint64 size; - - /** - * Read the "size" attribute from the specified header, converting it - * to the host byte order if needed. - */ - constexpr - uint64_t GetSize() const { - return size.Read(); - } -}; - -/** struct for DSDIFF native Artist and Title tags */ -struct dsdiff_native_tag { - uint32_t size; -}; - -struct DsdiffMetaData { - unsigned sample_rate, channels; - bool bitreverse; - uint64_t chunk_size; -#ifdef HAVE_ID3TAG - InputStream::offset_type id3_offset; - uint64_t id3_size; -#endif - /** offset for artist tag */ - InputStream::offset_type diar_offset; - /** offset for title tag */ - InputStream::offset_type diti_offset; -}; - -static bool lsbitfirst; - -static bool -dsdiff_init(const config_param ¶m) -{ - lsbitfirst = param.GetBlockValue("lsbitfirst", false); - return true; -} - -static bool -dsdiff_read_id(Decoder *decoder, InputStream &is, - DsdId *id) -{ - return decoder_read_full(decoder, is, id, sizeof(*id)); -} - -static bool -dsdiff_read_chunk_header(Decoder *decoder, InputStream &is, - DsdiffChunkHeader *header) -{ - return decoder_read_full(decoder, is, header, sizeof(*header)); -} - -static bool -dsdiff_read_payload(Decoder *decoder, InputStream &is, - const DsdiffChunkHeader *header, - void *data, size_t length) -{ - uint64_t size = header->GetSize(); - if (size != (uint64_t)length) - return false; - - return decoder_read_full(decoder, is, data, length); -} - -/** - * Read and parse a "SND" chunk inside "PROP". - */ -static bool -dsdiff_read_prop_snd(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - InputStream::offset_type end_offset) -{ - DsdiffChunkHeader header; - while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) { - if (!dsdiff_read_chunk_header(decoder, is, &header)) - return false; - - InputStream::offset_type chunk_end_offset = is.GetOffset() - + header.GetSize(); - if (chunk_end_offset > end_offset) - return false; - - if (header.id.Equals("FS ")) { - uint32_t sample_rate; - if (!dsdiff_read_payload(decoder, is, &header, - &sample_rate, - sizeof(sample_rate))) - return false; - - metadata->sample_rate = FromBE32(sample_rate); - } else if (header.id.Equals("CHNL")) { - uint16_t channels; - if (header.GetSize() < sizeof(channels) || - !decoder_read_full(decoder, is, - &channels, sizeof(channels)) || - !dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - - metadata->channels = FromBE16(channels); - } else if (header.id.Equals("CMPR")) { - DsdId type; - if (header.GetSize() < sizeof(type) || - !decoder_read_full(decoder, is, - &type, sizeof(type)) || - !dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - - if (!type.Equals("DSD ")) - /* only uncompressed DSD audio data - is implemented */ - return false; - } else { - /* ignore unknown chunk */ - - if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - } - } - - return is.GetOffset() == end_offset; -} - -/** - * Read and parse a "PROP" chunk. - */ -static bool -dsdiff_read_prop(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - const DsdiffChunkHeader *prop_header) -{ - uint64_t prop_size = prop_header->GetSize(); - InputStream::offset_type end_offset = is.GetOffset() + prop_size; - - DsdId prop_id; - if (prop_size < sizeof(prop_id) || - !dsdiff_read_id(decoder, is, &prop_id)) - return false; - - if (prop_id.Equals("SND ")) - return dsdiff_read_prop_snd(decoder, is, metadata, end_offset); - else - /* ignore unknown PROP chunk */ - return dsdlib_skip_to(decoder, is, end_offset); -} - -static void -dsdiff_handle_native_tag(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, InputStream::offset_type tagoffset, - TagType type) -{ - if (!dsdlib_skip_to(nullptr, is, tagoffset)) - return; - - struct dsdiff_native_tag metatag; - - if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag))) - return; - - uint32_t length = FromBE32(metatag.size); - - /* Check and limit size of the tag to prevent a stack overflow */ - if (length == 0 || length > 60) - return; - - char string[length]; - char *label; - label = string; - - if (!decoder_read_full(nullptr, is, label, (size_t)length)) - return; - - string[length] = '\0'; - tag_handler_invoke_tag(handler, handler_ctx, type, label); - return; -} - -/** - * Read and parse additional metadata chunks for tagging purposes. By default - * dsdiff files only support equivalents for artist and title but some of the - * extract tools add an id3 tag to provide more tags. If such id3 is found - * this will be used for tagging otherwise the native tags (if any) will be - * used - */ - -static bool -dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - DsdiffChunkHeader *chunk_header, - const struct tag_handler *handler, - void *handler_ctx) -{ - - /* skip from DSD data to next chunk header */ - if (!dsdlib_skip(decoder, is, metadata->chunk_size)) - return false; - if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) - return false; - - metadata->diar_offset = 0; - metadata->diti_offset = 0; - -#ifdef HAVE_ID3TAG - metadata->id3_offset = 0; -#endif - - /* Now process all the remaining chunk headers in the stream - and record their position and size */ - - do { - uint64_t chunk_size = chunk_header->GetSize(); - - /* DIIN chunk, is directly followed by other chunks */ - if (chunk_header->id.Equals("DIIN")) - chunk_size = 0; - - /* DIAR chunk - DSDIFF native tag for Artist */ - if (chunk_header->id.Equals("DIAR")) { - chunk_size = chunk_header->GetSize(); - metadata->diar_offset = is.GetOffset(); - } - - /* DITI chunk - DSDIFF native tag for Title */ - if (chunk_header->id.Equals("DITI")) { - chunk_size = chunk_header->GetSize(); - metadata->diti_offset = is.GetOffset(); - } -#ifdef HAVE_ID3TAG - /* 'ID3 ' chunk, offspec. Used by sacdextract */ - if (chunk_header->id.Equals("ID3 ")) { - chunk_size = chunk_header->GetSize(); - metadata->id3_offset = is.GetOffset(); - metadata->id3_size = chunk_size; - } -#endif - - if (!dsdlib_skip(decoder, is, chunk_size)) - break; - } while (dsdiff_read_chunk_header(decoder, is, chunk_header)); - - /* done processing chunk headers, process tags if any */ - -#ifdef HAVE_ID3TAG - if (metadata->id3_offset != 0) - { - /* a ID3 tag has preference over the other tags, do not process - other tags if we have one */ - dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset); - return true; - } -#endif - - if (metadata->diar_offset != 0) - dsdiff_handle_native_tag(is, handler, handler_ctx, - metadata->diar_offset, TAG_ARTIST); - - if (metadata->diti_offset != 0) - dsdiff_handle_native_tag(is, handler, handler_ctx, - metadata->diti_offset, TAG_TITLE); - return true; -} - -/** - * Read and parse all metadata chunks at the beginning. Stop when the - * first "DSD" chunk is seen, and return its header in the - * "chunk_header" parameter. - */ -static bool -dsdiff_read_metadata(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - DsdiffChunkHeader *chunk_header) -{ - DsdiffHeader header; - if (!decoder_read_full(decoder, is, &header, sizeof(header)) || - !header.id.Equals("FRM8") || - !header.format.Equals("DSD ")) - return false; - - while (true) { - if (!dsdiff_read_chunk_header(decoder, is, - chunk_header)) - return false; - - if (chunk_header->id.Equals("PROP")) { - if (!dsdiff_read_prop(decoder, is, metadata, - chunk_header)) - return false; - } else if (chunk_header->id.Equals("DSD ")) { - const uint64_t chunk_size = chunk_header->GetSize(); - metadata->chunk_size = chunk_size; - return true; - } else { - /* ignore unknown chunk */ - const uint64_t chunk_size = chunk_header->GetSize(); - InputStream::offset_type chunk_end_offset = - is.GetOffset() + chunk_size; - - if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - } - } -} - -static void -bit_reverse_buffer(uint8_t *p, uint8_t *end) -{ - for (; p < end; ++p) - *p = bit_reverse(*p); -} - -/** - * Decode one "DSD" chunk. - */ -static bool -dsdiff_decode_chunk(Decoder &decoder, InputStream &is, - unsigned channels, - uint64_t chunk_size) -{ - uint8_t buffer[8192]; - - const size_t sample_size = sizeof(buffer[0]); - const size_t frame_size = channels * sample_size; - const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; - - while (chunk_size >= frame_size) { - /* see how much aligned data from the remaining chunk - fits into the local buffer */ - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned now_frames = - (unsigned)chunk_size / frame_size; - now_size = now_frames * frame_size; - } - - if (!decoder_read_full(&decoder, is, buffer, now_size)) - return false; - - const size_t nbytes = now_size; - chunk_size -= nbytes; - - if (lsbitfirst) - bit_reverse_buffer(buffer, buffer + nbytes); - - const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DecoderCommand::NONE: - break; - - case DecoderCommand::START: - case DecoderCommand::STOP: - return false; - - case DecoderCommand::SEEK: - - /* Not implemented yet */ - decoder_seek_error(decoder); - break; - } - } - return dsdlib_skip(&decoder, is, chunk_size); -} - -static void -dsdiff_stream_decode(Decoder &decoder, InputStream &is) -{ - DsdiffMetaData metadata; - - DsdiffChunkHeader chunk_header; - /* check if it is is a proper DFF file */ - if (!dsdiff_read_metadata(&decoder, is, &metadata, &chunk_header)) - return; - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, error)) { - LogError(error); - return; - } - - /* calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; - - /* success: file was recognized */ - decoder_initialized(decoder, audio_format, false, songtime); - - /* every iteration of the following loop decodes one "DSD" - chunk from a DFF file */ - - while (true) { - chunk_size = chunk_header.GetSize(); - - if (chunk_header.id.Equals("DSD ")) { - if (!dsdiff_decode_chunk(decoder, is, - metadata.channels, - chunk_size)) - break; - } else { - /* ignore other chunks */ - if (!dsdlib_skip(&decoder, is, chunk_size)) - break; - } - - /* read next chunk header; the first one was read by - dsdiff_read_metadata() */ - if (!dsdiff_read_chunk_header(&decoder, - is, &chunk_header)) - break; - } -} - -static bool -dsdiff_scan_stream(InputStream &is, - gcc_unused const struct tag_handler *handler, - gcc_unused void *handler_ctx) -{ - DsdiffMetaData metadata; - DsdiffChunkHeader chunk_header; - - /* First check for DFF metadata */ - if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header)) - return false; - - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, IgnoreError())) - /* refuse to parse files which we cannot play anyway */ - return false; - - /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; - tag_handler_invoke_duration(handler, handler_ctx, songtime); - - /* Read additional metadata and created tags if available */ - dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header, - handler, handler_ctx); - - return true; -} - -static const char *const dsdiff_suffixes[] = { - "dff", - nullptr -}; - -static const char *const dsdiff_mime_types[] = { - "application/x-dff", - nullptr -}; - -const struct DecoderPlugin dsdiff_decoder_plugin = { - "dsdiff", - dsdiff_init, - nullptr, - dsdiff_stream_decode, - nullptr, - nullptr, - dsdiff_scan_stream, - nullptr, - dsdiff_suffixes, - dsdiff_mime_types, -}; diff --git a/src/decoder/DsdiffDecoderPlugin.hxx b/src/decoder/DsdiffDecoderPlugin.hxx deleted file mode 100644 index be14fc9cd..000000000 --- a/src/decoder/DsdiffDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_DSDIFF_H -#define MPD_DECODER_DSDIFF_H - -extern const struct DecoderPlugin dsdiff_decoder_plugin; - -#endif diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx deleted file mode 100644 index 9fbfe9cda..000000000 --- a/src/decoder/DsfDecoderPlugin.cxx +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2003-2013 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. - */ - -/* \file - * - * This plugin decodes DSDIFF data (SACD) embedded in DSF files. - * - * The DSF code was created using the specification found here: - * http://dsd-guide.com/sonys-dsf-file-format-spec - * - * All functions common to both DSD decoders have been moved to dsdlib - */ - -#include "config.h" -#include "DsfDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "util/bit_reverse.h" -#include "util/Error.hxx" -#include "system/ByteOrder.hxx" -#include "DsdLib.hxx" -#include "tag/TagHandler.hxx" -#include "Log.hxx" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -struct DsfMetaData { - unsigned sample_rate, channels; - bool bitreverse; - uint64_t chunk_size; -#ifdef HAVE_ID3TAG - InputStream::offset_type id3_offset; - uint64_t id3_size; -#endif -}; - -struct DsfHeader { - /** DSF header id: "DSD " */ - DsdId id; - /** DSD chunk size, including id = 28 */ - DsdUint64 size; - /** total file size */ - DsdUint64 fsize; - /** pointer to id3v2 metadata, should be at the end of the file */ - DsdUint64 pmeta; -}; - -/** DSF file fmt chunk */ -struct DsfFmtChunk { - /** id: "fmt " */ - DsdId id; - /** fmt chunk size, including id, normally 52 */ - DsdUint64 size; - /** version of this format = 1 */ - uint32_t version; - /** 0: DSD raw */ - uint32_t formatid; - /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */ - uint32_t channeltype; - /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */ - uint32_t channelnum; - /** sample frequency: 2822400, 5644800 */ - uint32_t sample_freq; - /** bits per sample 1 or 8 */ - uint32_t bitssample; - /** Sample count per channel in bytes */ - DsdUint64 scnt; - /** block size per channel = 4096 */ - uint32_t block_size; - /** reserved, should be all zero */ - uint32_t reserved; -}; - -struct DsfDataChunk { - DsdId id; - /** "data" chunk size, includes header (id+size) */ - DsdUint64 size; -}; - -/** - * Read and parse all needed metadata chunks for DSF files. - */ -static bool -dsf_read_metadata(Decoder *decoder, InputStream &is, - DsfMetaData *metadata) -{ - DsfHeader dsf_header; - if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) || - !dsf_header.id.Equals("DSD ")) - return false; - - const uint64_t chunk_size = dsf_header.size.Read(); - if (sizeof(dsf_header) != chunk_size) - return false; - -#ifdef HAVE_ID3TAG - const uint64_t metadata_offset = dsf_header.pmeta.Read(); -#endif - - /* read the 'fmt ' chunk of the DSF file */ - DsfFmtChunk dsf_fmt_chunk; - if (!decoder_read_full(decoder, is, - &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || - !dsf_fmt_chunk.id.Equals("fmt ")) - return false; - - const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read(); - if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) - return false; - - uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq); - - /* for now, only support version 1 of the standard, DSD raw stereo - files with a sample freq of 2822400 or 5644800 Hz */ - - if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 - || dsf_fmt_chunk.channeltype != 2 - || dsf_fmt_chunk.channelnum != 2 - || (samplefreq != 2822400 && samplefreq != 5644800)) - return false; - - uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size); - /* according to the spec block size should always be 4096 */ - if (chblksize != 4096) - return false; - - /* read the 'data' chunk of the DSF file */ - DsfDataChunk data_chunk; - if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) || - !data_chunk.id.Equals("data")) - return false; - - /* data size of DSF files are padded to multiple of 4096, - we use the actual data size as chunk size */ - - uint64_t data_size = data_chunk.size.Read(); - if (data_size < sizeof(data_chunk)) - return false; - - data_size -= sizeof(data_chunk); - - /* data_size cannot be bigger or equal to total file size */ - const uint64_t size = (uint64_t)is.GetSize(); - if (data_size >= size) - return false; - - /* use the sample count from the DSF header as the upper - bound, because some DSF files contain junk at the end of - the "data" chunk */ - const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read(); - const uint64_t playable_size = samplecnt * 2 / 8; - if (data_size > playable_size) - data_size = playable_size; - - metadata->chunk_size = data_size; - metadata->channels = (unsigned) dsf_fmt_chunk.channelnum; - metadata->sample_rate = samplefreq; -#ifdef HAVE_ID3TAG - /* metada_offset cannot be bigger then or equal to total file size */ - if (metadata_offset >= size) - metadata->id3_offset = 0; - else - metadata->id3_offset = (InputStream::offset_type)metadata_offset; -#endif - /* check bits per sample format, determine if bitreverse is needed */ - metadata->bitreverse = dsf_fmt_chunk.bitssample == 1; - return true; -} - -static void -bit_reverse_buffer(uint8_t *p, uint8_t *end) -{ - for (; p < end; ++p) - *p = bit_reverse(*p); -} - -/** - * DSF data is build up of alternating 4096 blocks of DSD samples for left and - * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1 - * block of 4096 DSD right samples to 8k of samples in normal PCM left/right - * order. - */ -static void -dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes) -{ - for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) { - scratch[i] = *(dest+j); - j++; - } - - for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) { - scratch[i] = *(dest+4096+j); - j++; - } - - for (unsigned i = 0; i < (unsigned)nrbytes; i++) { - *dest = scratch[i]; - dest++; - } -} - -/** - * Decode one complete DSF 'data' chunk i.e. a complete song - */ -static bool -dsf_decode_chunk(Decoder &decoder, InputStream &is, - unsigned channels, - uint64_t chunk_size, - bool bitreverse) -{ - uint8_t buffer[8192]; - - /* scratch buffer for DSF samples to convert to the needed - normal left/right regime of samples */ - uint8_t dsf_scratch_buffer[8192]; - - const size_t sample_size = sizeof(buffer[0]); - const size_t frame_size = channels * sample_size; - const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; - - while (chunk_size >= frame_size) { - /* see how much aligned data from the remaining chunk - fits into the local buffer */ - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned now_frames = - (unsigned)chunk_size / frame_size; - now_size = now_frames * frame_size; - } - - if (!decoder_read_full(&decoder, is, buffer, now_size)) - return false; - - const size_t nbytes = now_size; - chunk_size -= nbytes; - - if (bitreverse) - bit_reverse_buffer(buffer, buffer + nbytes); - - dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes); - - const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DecoderCommand::NONE: - break; - - case DecoderCommand::START: - case DecoderCommand::STOP: - return false; - - case DecoderCommand::SEEK: - - /* not implemented yet */ - decoder_seek_error(decoder); - break; - } - } - return dsdlib_skip(&decoder, is, chunk_size); -} - -static void -dsf_stream_decode(Decoder &decoder, InputStream &is) -{ - /* check if it is a proper DSF file */ - DsfMetaData metadata; - if (!dsf_read_metadata(&decoder, is, &metadata)) - return; - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, error)) { - LogError(error); - return; - } - /* Calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; - - /* success: file was recognized */ - decoder_initialized(decoder, audio_format, false, songtime); - - if (!dsf_decode_chunk(decoder, is, metadata.channels, - chunk_size, - metadata.bitreverse)) - return; -} - -static bool -dsf_scan_stream(InputStream &is, - gcc_unused const struct tag_handler *handler, - gcc_unused void *handler_ctx) -{ - /* check DSF metadata */ - DsfMetaData metadata; - if (!dsf_read_metadata(nullptr, is, &metadata)) - return false; - - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, IgnoreError())) - /* refuse to parse files which we cannot play anyway */ - return false; - - /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; - tag_handler_invoke_duration(handler, handler_ctx, songtime); - -#ifdef HAVE_ID3TAG - /* Add available tags from the ID3 tag */ - dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset); -#endif - return true; -} - -static const char *const dsf_suffixes[] = { - "dsf", - nullptr -}; - -static const char *const dsf_mime_types[] = { - "application/x-dsf", - nullptr -}; - -const struct DecoderPlugin dsf_decoder_plugin = { - "dsf", - nullptr, - nullptr, - dsf_stream_decode, - nullptr, - nullptr, - dsf_scan_stream, - nullptr, - dsf_suffixes, - dsf_mime_types, -}; diff --git a/src/decoder/DsfDecoderPlugin.hxx b/src/decoder/DsfDecoderPlugin.hxx deleted file mode 100644 index 921c94698..000000000 --- a/src/decoder/DsfDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_DSF_H -#define MPD_DECODER_DSF_H - -extern const struct DecoderPlugin dsf_decoder_plugin; - -#endif diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx deleted file mode 100644 index ae1181b4c..000000000 --- a/src/decoder/FaadDecoderPlugin.cxx +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FaadDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "DecoderBuffer.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <neaacdec.h> - -#include <assert.h> -#include <string.h> -#include <unistd.h> - -static const unsigned adts_sample_rates[] = - { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, 7350, 0, 0, 0 -}; - -static constexpr Domain faad_decoder_domain("faad_decoder"); - -/** - * 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(DecoderBuffer *buffer) -{ - while (true) { - size_t length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &length); - if (data == nullptr || length < 8) { - /* not enough data yet */ - if (!decoder_buffer_fill(buffer)) - /* failed */ - return 0; - - continue; - } - - /* find the 0xff marker */ - const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length); - if (p == nullptr) { - /* no marker - discard the buffer */ - decoder_buffer_clear(buffer); - continue; - } - - if (p > data) { - /* discard data before 0xff */ - decoder_buffer_consume(buffer, p - data); - continue; - } - - /* is it a frame? */ - const size_t 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 */ - if (!decoder_buffer_fill(buffer)) { - /* not enough data; discard this frame - to prevent a possible buffer - overflow */ - decoder_buffer_clear(buffer); - } - - continue; - } - - /* found a full frame! */ - return frame_length; - } -} - -static float -adts_song_duration(DecoderBuffer *buffer) -{ - const InputStream &is = decoder_buffer_get_stream(buffer); - const bool estimate = !is.CheapSeeking(); - const auto file_size = is.GetSize(); - if (estimate && file_size <= 0) - return -1; - - unsigned sample_rate = 0; - - /* Read all frames to ensure correct time and bitrate */ - unsigned frames; - for (frames = 0;; frames++) { - const unsigned frame_length = adts_find_frame(buffer); - if (frame_length == 0) - break; - - - if (frames == 0) { - size_t buffer_length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &buffer_length); - assert(data != nullptr); - assert(frame_length <= buffer_length); - - sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; - if (sample_rate == 0) - break; - } - - decoder_buffer_consume(buffer, frame_length); - - if (estimate && frames == 128) { - /* if this is a remote file, don't slurp the - whole file just for checking the song - duration; instead, stop after some time and - extrapolate the song duration from what we - have until now */ - - const auto offset = is.GetOffset() - - decoder_buffer_available(buffer); - if (offset <= 0) - return -1; - - frames = (frames * file_size) / offset; - break; - } - } - - if (sample_rate == 0) - return -1; - - float frames_per_second = (float)sample_rate / 1024.0; - assert(frames_per_second > 0); - - return (float)frames / frames_per_second; -} - -static float -faad_song_duration(DecoderBuffer *buffer, InputStream &is) -{ - const auto size = is.GetSize(); - const size_t fileread = size >= 0 ? size : 0; - - decoder_buffer_fill(buffer); - size_t length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) - return -1; - - size_t 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; - - const bool success = decoder_buffer_skip(buffer, tagsize) && - decoder_buffer_fill(buffer); - if (!success) - return -1; - - data = (const uint8_t *)decoder_buffer_read(buffer, &length); - if (data == nullptr) - return -1; - } - - if (length >= 8 && adts_check_frame(data) > 0) { - /* obtain the duration from the ADTS header */ - - if (!is.IsSeekable()) - return -1; - - float song_length = adts_song_duration(buffer); - - is.LockSeek(tagsize, SEEK_SET, IgnoreError()); - decoder_buffer_clear(buffer); - - return song_length; - } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { - /* obtain the duration from the ADIF header */ - size_t skip_size = (data[4] & 0x80) ? 9 : 0; - - if (8 + skip_size > length) - /* not enough data yet; skip parsing this - header */ - return -1; - - unsigned 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; -} - -static NeAACDecHandle -faad_decoder_new() -{ - const NeAACDecHandle decoder = NeAACDecOpen(); - - NeAACDecConfigurationPtr config = - NeAACDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; - config->downMatrix = 1; - config->dontUpSampleImplicitSBR = 0; - NeAACDecSetConfiguration(decoder, config); - - return decoder; -} - -/** - * Wrapper for NeAACDecInit() which works around some API - * inconsistencies in libfaad. - */ -static bool -faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, - AudioFormat &audio_format, Error &error) -{ - - size_t length; - const unsigned char *data = (const unsigned char *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) { - error.Set(faad_decoder_domain, "Empty file"); - return false; - } - - uint8_t channels; - unsigned long sample_rate; - long nbytes = NeAACDecInit(decoder, - /* deconst hack, libfaad requires this */ - const_cast<unsigned char *>(data), - length, - &sample_rate, &channels); - if (nbytes < 0) { - error.Set(faad_decoder_domain, "Not an AAC stream"); - return false; - } - - decoder_buffer_consume(buffer, nbytes); - - return audio_format_init_checked(audio_format, sample_rate, - SampleFormat::S16, channels, error); -} - -/** - * Wrapper for NeAACDecDecode() which works around some API - * inconsistencies in libfaad. - */ -static const void * -faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, - NeAACDecFrameInfo *frame_info) -{ - size_t length; - const unsigned char *data = (const unsigned char *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) - return nullptr; - - return NeAACDecDecode(decoder, frame_info, - /* deconst hack, libfaad requires this */ - const_cast<unsigned char *>(data), - length); -} - -/** - * 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(InputStream &is) -{ - DecoderBuffer *const buffer = - decoder_buffer_new(nullptr, is, - FAAD_MIN_STREAMSIZE * MAX_CHANNELS); - - float length = faad_song_duration(buffer, is); - - if (length < 0) { - AudioFormat audio_format; - - NeAACDecHandle decoder = faad_decoder_new(); - - decoder_buffer_fill(buffer); - - if (faad_decoder_init(decoder, buffer, audio_format, - IgnoreError())) - length = 0; - - NeAACDecClose(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(InputStream &is) -{ - float length = faad_get_file_time_float(is); - if (length < 0) - return -1; - - return int(length + 0.5); -} - -static void -faad_stream_decode(Decoder &mpd_decoder, InputStream &is) -{ - DecoderBuffer *const buffer = - decoder_buffer_new(&mpd_decoder, is, - FAAD_MIN_STREAMSIZE * MAX_CHANNELS); - - const float total_time = faad_song_duration(buffer, is); - - /* create the libfaad decoder */ - - const NeAACDecHandle decoder = faad_decoder_new(); - - while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() && - decoder_get_command(mpd_decoder) == DecoderCommand::NONE) { - adts_find_frame(buffer); - decoder_buffer_fill(buffer); - } - - /* initialize it */ - - Error error; - AudioFormat audio_format; - if (!faad_decoder_init(decoder, buffer, audio_format, error)) { - LogError(error); - NeAACDecClose(decoder); - decoder_buffer_free(buffer); - return; - } - - /* initialize the MPD core */ - - decoder_initialized(mpd_decoder, audio_format, false, total_time); - - /* the decoder loop */ - - DecoderCommand cmd; - uint16_t bit_rate = 0; - do { - /* find the next frame */ - - const size_t frame_size = adts_find_frame(buffer); - if (frame_size == 0) - /* end of file */ - break; - - /* decode it */ - - NeAACDecFrameInfo frame_info; - const void *const decoded = - faad_decoder_decode(decoder, buffer, &frame_info); - - if (frame_info.error > 0) { - FormatWarning(faad_decoder_domain, - "error decoding AAC stream: %s", - NeAACDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - FormatDefault(faad_decoder_domain, - "channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - - if (frame_info.samplerate != audio_format.sample_rate) { - FormatDefault(faad_decoder_domain, - "sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } - - 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 != DecoderCommand::STOP); - - /* cleanup */ - - NeAACDecClose(decoder); - decoder_buffer_free(buffer); -} - -static bool -faad_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - int file_time = faad_get_file_time(is); - if (file_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, file_time); - return true; -} - -static const char *const faad_suffixes[] = { "aac", nullptr }; -static const char *const faad_mime_types[] = { - "audio/aac", "audio/aacp", nullptr -}; - -const struct DecoderPlugin faad_decoder_plugin = { - "faad", - nullptr, - nullptr, - faad_stream_decode, - nullptr, - nullptr, - faad_scan_stream, - nullptr, - faad_suffixes, - faad_mime_types, -}; diff --git a/src/decoder/FaadDecoderPlugin.hxx b/src/decoder/FaadDecoderPlugin.hxx deleted file mode 100644 index 817927d5e..000000000 --- a/src/decoder/FaadDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FAAD_DECODER_PLUGIN_HXX -#define MPD_FAAD_DECODER_PLUGIN_HXX - -extern const struct DecoderPlugin faad_decoder_plugin; - -#endif diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx deleted file mode 100644 index 69e7a6c8b..000000000 --- a/src/decoder/FfmpegDecoderPlugin.cxx +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright (C) 2003-2013 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. - */ - -/* necessary because libavutil/common.h uses UINT64_C */ -#define __STDC_CONSTANT_MACROS - -#include "config.h" -#include "FfmpegDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "FfmpegMetaData.hxx" -#include "tag/TagHandler.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "LogV.hxx" - -extern "C" { -#include <libavcodec/avcodec.h> -#include <libavformat/avformat.h> -#include <libavformat/avio.h> -#include <libavutil/avutil.h> -#include <libavutil/log.h> -#include <libavutil/mathematics.h> - -#if LIBAVUTIL_VERSION_MAJOR >= 53 -#include <libavutil/frame.h> -#endif -} - -#include <assert.h> -#include <string.h> - -static constexpr Domain ffmpeg_domain("ffmpeg"); - -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - -static LogLevel -import_ffmpeg_level(int level) -{ - if (level <= AV_LOG_FATAL) - return LogLevel::ERROR; - - if (level <= AV_LOG_WARNING) - return LogLevel::WARNING; - - if (level <= AV_LOG_INFO) - return LogLevel::INFO; - - return LogLevel::DEBUG; -} - -static void -mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level, - const char *fmt, va_list vl) -{ - const AVClass * cls = nullptr; - - if (ptr != nullptr) - cls = *(const AVClass *const*)ptr; - - if (cls != nullptr) { - char domain[64]; - snprintf(domain, sizeof(domain), "%s/%s", - ffmpeg_domain.GetName(), cls->item_name(ptr)); - const Domain d(domain); - LogFormatV(d, import_ffmpeg_level(level), fmt, vl); - } -} - -struct AvioStream { - Decoder *const decoder; - InputStream &input; - - AVIOContext *io; - - unsigned char buffer[8192]; - - AvioStream(Decoder *_decoder, InputStream &_input) - :decoder(_decoder), input(_input), io(nullptr) {} - - ~AvioStream() { - if (io != nullptr) - av_free(io); - } - - bool Open(); -}; - -static int -mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) -{ - AvioStream *stream = (AvioStream *)opaque; - - return decoder_read(stream->decoder, stream->input, - (void *)buf, size); -} - -static int64_t -mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) -{ - AvioStream *stream = (AvioStream *)opaque; - - if (whence == AVSEEK_SIZE) - return stream->input.size; - - if (!stream->input.LockSeek(pos, whence, IgnoreError())) - return -1; - - return stream->input.offset; -} - -bool -AvioStream::Open() -{ - io = avio_alloc_context(buffer, sizeof(buffer), - false, this, - mpd_ffmpeg_stream_read, nullptr, - input.seekable - ? mpd_ffmpeg_stream_seek : nullptr); - return io != nullptr; -} - -/** - * API compatibility wrapper for av_open_input_stream() and - * avformat_open_input(). - */ -static int -mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, - AVIOContext *pb, - const char *filename, - AVInputFormat *fmt) -{ - AVFormatContext *context = avformat_alloc_context(); - if (context == nullptr) - return AVERROR(ENOMEM); - - context->pb = pb; - *ic_ptr = context; - return avformat_open_input(ic_ptr, filename, fmt, nullptr); -} - -static bool -ffmpeg_init(gcc_unused const config_param ¶m) -{ - av_log_set_callback(mpd_ffmpeg_log_callback); - - av_register_all(); - 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 == - AVMEDIA_TYPE_AUDIO) - return i; - - return -1; -} - -gcc_const -static double -time_from_ffmpeg(int64_t t, const AVRational time_base) -{ - assert(t != (int64_t)AV_NOPTS_VALUE); - - return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) - / (double)1024; -} - -gcc_const -static int64_t -time_to_ffmpeg(double t, const AVRational time_base) -{ - return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, - time_base); -} - -/** - * Replace #AV_NOPTS_VALUE with the given fallback. - */ -static constexpr int64_t -timestamp_fallback(int64_t t, int64_t fallback) -{ - return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) - ? t - : fallback; -} - -/** - * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with - * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply - * assume that the stream's start time is zero, which appears to be - * the best way out of that situation. - */ -static int64_t -start_time_fallback(const AVStream &stream) -{ - return timestamp_fallback(stream.start_time, 0); -} - -static void -copy_interleave_frame2(uint8_t *dest, uint8_t **src, - unsigned nframes, unsigned nchannels, - unsigned sample_size) -{ - for (unsigned frame = 0; frame < nframes; ++frame) { - for (unsigned channel = 0; channel < nchannels; ++channel) { - memcpy(dest, src[channel] + frame * sample_size, - sample_size); - dest += sample_size; - } - } -} - -/** - * Copy PCM data from a AVFrame to an interleaved buffer. - */ -static int -copy_interleave_frame(const AVCodecContext *codec_context, - const AVFrame *frame, - uint8_t **output_buffer, - uint8_t **global_buffer, int *global_buffer_size) -{ - int plane_size; - const int data_size = - av_samples_get_buffer_size(&plane_size, - codec_context->channels, - frame->nb_samples, - codec_context->sample_fmt, 1); - if (data_size <= 0) - return data_size; - - if (av_sample_fmt_is_planar(codec_context->sample_fmt) && - codec_context->channels > 1) { - if(*global_buffer_size < data_size) { - av_freep(global_buffer); - - *global_buffer = (uint8_t*)av_malloc(data_size); - - if (!*global_buffer) - /* Not enough memory - shouldn't happen */ - return AVERROR(ENOMEM); - *global_buffer_size = data_size; - } - *output_buffer = *global_buffer; - copy_interleave_frame2(*output_buffer, frame->extended_data, - frame->nb_samples, - codec_context->channels, - av_get_bytes_per_sample(codec_context->sample_fmt)); - } else { - *output_buffer = frame->extended_data[0]; - } - - return data_size; -} - -static DecoderCommand -ffmpeg_send_packet(Decoder &decoder, InputStream &is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVStream *stream, - AVFrame *frame, - uint8_t **buffer, int *buffer_size) -{ - if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) - decoder_timestamp(decoder, - time_from_ffmpeg(packet->pts - start_time_fallback(*stream), - stream->time_base)); - - AVPacket packet2 = *packet; - - uint8_t *output_buffer; - - DecoderCommand cmd = DecoderCommand::NONE; - while (packet2.size > 0 && cmd == DecoderCommand::NONE) { - int audio_size = 0; - int got_frame = 0; - int len = avcodec_decode_audio4(codec_context, - frame, &got_frame, - &packet2); - if (len >= 0 && got_frame) { - audio_size = copy_interleave_frame(codec_context, - frame, - &output_buffer, - buffer, buffer_size); - if (audio_size < 0) - len = audio_size; - } - - if (len < 0) { - /* if error, we skip the frame */ - LogDefault(ffmpeg_domain, - "decoding failed, frame skipped"); - break; - } - - packet2.data += len; - packet2.size -= len; - - if (audio_size <= 0) - continue; - - cmd = decoder_data(decoder, is, - output_buffer, audio_size, - codec_context->bit_rate / 1000); - } - return cmd; -} - -gcc_const -static SampleFormat -ffmpeg_sample_format(enum AVSampleFormat sample_fmt) -{ - switch (sample_fmt) { - case AV_SAMPLE_FMT_S16: - case AV_SAMPLE_FMT_S16P: - return SampleFormat::S16; - - case AV_SAMPLE_FMT_S32: - case AV_SAMPLE_FMT_S32P: - return SampleFormat::S32; - - case AV_SAMPLE_FMT_FLTP: - return SampleFormat::FLOAT; - - default: - break; - } - - char buffer[64]; - const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), - sample_fmt); - if (name != nullptr) - FormatError(ffmpeg_domain, - "Unsupported libavcodec SampleFormat value: %s (%d)", - name, sample_fmt); - else - FormatError(ffmpeg_domain, - "Unsupported libavcodec SampleFormat value: %d", - sample_fmt); - return SampleFormat::UNDEFINED; -} - -static AVInputFormat * -ffmpeg_probe(Decoder *decoder, InputStream &is) -{ - enum { - BUFFER_SIZE = 16384, - PADDING = 16, - }; - - unsigned char buffer[BUFFER_SIZE]; - size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); - if (nbytes <= PADDING || !is.LockRewind(IgnoreError())) - return nullptr; - - /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes - beyond the declared buffer limit, which makes valgrind - angry; this workaround removes some padding from the buffer - size */ - nbytes -= PADDING; - - AVProbeData avpd; - - /* new versions of ffmpeg may add new attributes, and leaving - them uninitialized may crash; hopefully, zero-initializing - everything we don't know is ok */ - memset(&avpd, 0, sizeof(avpd)); - - avpd.buf = buffer; - avpd.buf_size = nbytes; - avpd.filename = is.uri.c_str(); - -#ifdef AVPROBE_SCORE_MIME - /* this attribute was added in libav/ffmpeg version 11, but - unfortunately it's "uint8_t" instead of "char", and it's - not "const" - wtf? */ - avpd.mime_type = (uint8_t *)const_cast<char *>(is.GetMimeType()); -#endif - - return av_probe_input_format(&avpd, true); -} - -static void -ffmpeg_decode(Decoder &decoder, InputStream &input) -{ - AVInputFormat *input_format = ffmpeg_probe(&decoder, input); - if (input_format == nullptr) - return; - - FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", - input_format->name, input_format->long_name); - - AvioStream stream(&decoder, input); - if (!stream.Open()) { - LogError(ffmpeg_domain, "Failed to open stream"); - return; - } - - //ffmpeg works with ours "fileops" helper - AVFormatContext *format_context = nullptr; - if (mpd_ffmpeg_open_input(&format_context, stream.io, - input.uri.c_str(), - input_format) != 0) { - LogError(ffmpeg_domain, "Open failed"); - return; - } - - const int find_result = - avformat_find_stream_info(format_context, nullptr); - if (find_result < 0) { - LogError(ffmpeg_domain, "Couldn't find stream info"); - avformat_close_input(&format_context); - return; - } - - int audio_stream = ffmpeg_find_audio_stream(format_context); - if (audio_stream == -1) { - LogError(ffmpeg_domain, "No audio stream inside"); - avformat_close_input(&format_context); - return; - } - - AVStream *av_stream = format_context->streams[audio_stream]; - - AVCodecContext *codec_context = av_stream->codec; - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0) - const AVCodecDescriptor *codec_descriptor = - avcodec_descriptor_get(codec_context->codec_id); - if (codec_descriptor != nullptr) - FormatDebug(ffmpeg_domain, "codec '%s'", - codec_descriptor->name); -#else - if (codec_context->codec_name[0] != 0) - FormatDebug(ffmpeg_domain, "codec '%s'", - codec_context->codec_name); -#endif - - AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); - - if (!codec) { - LogError(ffmpeg_domain, "Unsupported audio codec"); - avformat_close_input(&format_context); - return; - } - - const SampleFormat sample_format = - ffmpeg_sample_format(codec_context->sample_fmt); - if (sample_format == SampleFormat::UNDEFINED) - return; - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, - codec_context->sample_rate, - sample_format, - codec_context->channels, error)) { - LogError(error); - avformat_close_input(&format_context); - return; - } - - /* the audio format must be read from AVCodecContext by now, - because avcodec_open() has been demonstrated to fill bogus - values into AVCodecContext.channels - a change that will be - reverted later by avcodec_decode_audio3() */ - - const int open_result = avcodec_open2(codec_context, codec, nullptr); - if (open_result < 0) { - LogError(ffmpeg_domain, "Could not open codec"); - avformat_close_input(&format_context); - return; - } - - int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE - ? format_context->duration / AV_TIME_BASE - : 0; - - decoder_initialized(decoder, audio_format, - input.seekable, total_time); - -#if LIBAVUTIL_VERSION_MAJOR >= 53 - AVFrame *frame = av_frame_alloc(); -#else - AVFrame *frame = avcodec_alloc_frame(); -#endif - if (!frame) { - LogError(ffmpeg_domain, "Could not allocate frame"); - avformat_close_input(&format_context); - return; - } - - uint8_t *interleaved_buffer = nullptr; - int interleaved_buffer_size = 0; - - DecoderCommand cmd; - do { - AVPacket packet; - if (av_read_frame(format_context, &packet) < 0) - /* end of file */ - break; - - if (packet.stream_index == audio_stream) - cmd = ffmpeg_send_packet(decoder, input, - &packet, codec_context, - av_stream, - frame, - &interleaved_buffer, &interleaved_buffer_size); - else - cmd = decoder_get_command(decoder); - - av_free_packet(&packet); - - if (cmd == DecoderCommand::SEEK) { - int64_t where = - time_to_ffmpeg(decoder_seek_where(decoder), - av_stream->time_base) + - start_time_fallback(*av_stream); - - if (av_seek_frame(format_context, audio_stream, where, - AVSEEK_FLAG_ANY) < 0) - decoder_seek_error(decoder); - else { - avcodec_flush_buffers(codec_context); - decoder_command_finished(decoder); - } - } - } while (cmd != DecoderCommand::STOP); - -#if LIBAVUTIL_VERSION_MAJOR >= 53 - av_frame_free(&frame); -#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) - avcodec_free_frame(&frame); -#else - av_freep(&frame); -#endif - av_freep(&interleaved_buffer); - - avcodec_close(codec_context); - avformat_close_input(&format_context); -} - -//no tag reading in ffmpeg, check if playable -static bool -ffmpeg_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - AVInputFormat *input_format = ffmpeg_probe(nullptr, is); - if (input_format == nullptr) - return false; - - AvioStream stream(nullptr, is); - if (!stream.Open()) - return false; - - AVFormatContext *f = nullptr; - if (mpd_ffmpeg_open_input(&f, stream.io, is.uri.c_str(), - input_format) != 0) - return false; - - const int find_result = - avformat_find_stream_info(f, nullptr); - if (find_result < 0) { - avformat_close_input(&f); - return false; - } - - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag_handler_invoke_duration(handler, handler_ctx, - f->duration / AV_TIME_BASE); - - ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); - int idx = ffmpeg_find_audio_stream(f); - if (idx >= 0) - ffmpeg_scan_dictionary(f->streams[idx]->metadata, - handler, handler_ctx); - - avformat_close_input(&f); - return true; -} - -/** - * 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", "m4b", "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", "opus", "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", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", - "wve", - nullptr -}; - -static const char *const ffmpeg_mime_types[] = { - "application/flv", - "application/m4a", - "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/aacp", - "audio/ac3", - "audio/aiff" - "audio/amr", - "audio/basic", - "audio/flac", - "audio/m4a", - "audio/mp4", - "audio/mpeg", - "audio/musepack", - "audio/ogg", - "audio/opus", - "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-matroska", - "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-voc", - "audio/x-wav", - "audio/x-wma", - "audio/x-wv", - "video/anim", - "video/quicktime", - "video/msvideo", - "video/ogg", - "video/theora", - "video/webm", - "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", - - /* special value for the "ffmpeg" input plugin: all streams by - the "ffmpeg" input plugin shall be decoded by this - plugin */ - "audio/x-mpd-ffmpeg", - - nullptr -}; - -const struct DecoderPlugin ffmpeg_decoder_plugin = { - "ffmpeg", - ffmpeg_init, - nullptr, - ffmpeg_decode, - nullptr, - nullptr, - ffmpeg_scan_stream, - nullptr, - ffmpeg_suffixes, - ffmpeg_mime_types -}; diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/FfmpegDecoderPlugin.hxx deleted file mode 100644 index 23bf74fce..000000000 --- a/src/decoder/FfmpegDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FFMPEG_HXX -#define MPD_DECODER_FFMPEG_HXX - -extern const struct DecoderPlugin ffmpeg_decoder_plugin; - -#endif diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx deleted file mode 100644 index 6e92b4a13..000000000 --- a/src/decoder/FfmpegMetaData.cxx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2003-2013 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. - */ - -/* necessary because libavutil/common.h uses UINT64_C */ -#define __STDC_CONSTANT_MACROS - -#include "config.h" -#include "FfmpegMetaData.hxx" -#include "tag/TagTable.hxx" -#include "tag/TagHandler.hxx" - -static const struct tag_table ffmpeg_tags[] = { - { "year", TAG_DATE }, - { "author-sort", TAG_ARTIST_SORT }, - { "album_artist", TAG_ALBUM_ARTIST }, - { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, - - /* sentinel */ - { nullptr, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -ffmpeg_copy_metadata(TagType type, - AVDictionary *m, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *mt = nullptr; - - while ((mt = av_dict_get(m, name, mt, 0)) != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - type, mt->value); -} - -static void -ffmpeg_scan_pairs(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *i = nullptr; - - while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr) - tag_handler_invoke_pair(handler, handler_ctx, - i->key, i->value); -} - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], - handler, handler_ctx); - - for (const struct tag_table *i = ffmpeg_tags; - i->name != nullptr; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); - - if (handler->pair != nullptr) - ffmpeg_scan_pairs(dict, handler, handler_ctx); -} diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx deleted file mode 100644 index 998cdf5a8..000000000 --- a/src/decoder/FfmpegMetaData.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FFMPEG_METADATA_HXX -#define MPD_FFMPEG_METADATA_HXX - -extern "C" { -#include <libavutil/dict.h> -} - -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - -struct tag_handler; - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx deleted file mode 100644 index e4b906c12..000000000 --- a/src/decoder/FlacCommon.cxx +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 - */ - -#include "config.h" -#include "FlacCommon.hxx" -#include "FlacMetadata.hxx" -#include "FlacPcm.hxx" -#include "MixRampInfo.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> - -flac_data::flac_data(Decoder &_decoder, - InputStream &_input_stream) - :FlacInput(_input_stream, &_decoder), - initialized(false), unsupported(false), - total_frames(0), first_frame(0), next_frame(0), position(0), - decoder(_decoder), input_stream(_input_stream) -{ -} - -static SampleFormat -flac_sample_format(unsigned bits_per_sample) -{ - switch (bits_per_sample) { - case 8: - return SampleFormat::S8; - - case 16: - return SampleFormat::S16; - - case 24: - return SampleFormat::S24_P32; - - case 32: - return SampleFormat::S32; - - default: - return SampleFormat::UNDEFINED; - } -} - -static void -flac_got_stream_info(struct flac_data *data, - const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - if (data->initialized || data->unsupported) - return; - - Error error; - if (!audio_format_init_checked(data->audio_format, - stream_info->sample_rate, - flac_sample_format(stream_info->bits_per_sample), - stream_info->channels, error)) { - LogError(error); - data->unsupported = true; - return; - } - - data->frame_size = data->audio_format.GetFrameSize(); - - if (data->total_frames == 0) - data->total_frames = stream_info->total_samples; - - data->initialized = true; -} - -void flac_metadata_common_cb(const FLAC__StreamMetadata * block, - struct flac_data *data) -{ - if (data->unsupported) - return; - - ReplayGainInfo rgi; - - switch (block->type) { - case FLAC__METADATA_TYPE_STREAMINFO: - flac_got_stream_info(data, &block->data.stream_info); - break; - - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - if (flac_parse_replay_gain(rgi, block)) - decoder_replay_gain(data->decoder, &rgi); - - decoder_mixramp(data->decoder, flac_parse_mixramp(block)); - - flac_vorbis_comments_to_tag(data->tag, - &block->data.vorbis_comment); - - default: - break; - } -} - -/** - * This function attempts to call decoder_initialized() in case there - * was no STREAMINFO block. This is allowed for nonseekable streams, - * where the server sends us only a part of the file, without - * providing the STREAMINFO block from the beginning of the file - * (e.g. when seeking with SqueezeBox Server). - */ -static bool -flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) -{ - if (data->unsupported) - return false; - - Error error; - if (!audio_format_init_checked(data->audio_format, - header->sample_rate, - flac_sample_format(header->bits_per_sample), - header->channels, error)) { - LogError(error); - data->unsupported = true; - return false; - } - - data->frame_size = data->audio_format.GetFrameSize(); - - decoder_initialized(data->decoder, data->audio_format, - data->input_stream.seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); - - data->initialized = true; - - return true; -} - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[], - FLAC__uint64 nbytes) -{ - void *buffer; - unsigned bit_rate; - - if (!data->initialized && !flac_got_first_frame(data, &frame->header)) - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - size_t buffer_size = frame->header.blocksize * data->frame_size; - buffer = data->buffer.Get(buffer_size); - - flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, - 0, frame->header.blocksize); - - if (nbytes > 0) - bit_rate = nbytes * 8 * frame->header.sample_rate / - (1000 * frame->header.blocksize); - else - bit_rate = 0; - - auto cmd = decoder_data(data->decoder, data->input_stream, - buffer, buffer_size, - bit_rate); - data->next_frame += frame->header.blocksize; - switch (cmd) { - case DecoderCommand::NONE: - case DecoderCommand::START: - break; - - case DecoderCommand::STOP: - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - case DecoderCommand::SEEK: - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; - } - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx deleted file mode 100644 index de000dfa1..000000000 --- a/src/decoder/FlacCommon.hxx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_COMMON_HXX -#define MPD_FLAC_COMMON_HXX - -#include "FlacInput.hxx" -#include "DecoderAPI.hxx" -#include "pcm/PcmBuffer.hxx" - -#include <FLAC/stream_decoder.h> -#include <FLAC/metadata.h> - -struct flac_data : public FlacInput { - PcmBuffer buffer; - - /** - * The size of one frame in the output buffer. - */ - unsigned frame_size; - - /** - * Has decoder_initialized() been called yet? - */ - bool initialized; - - /** - * Does the FLAC file contain an unsupported audio format? - */ - bool unsupported; - - /** - * The validated audio format of the FLAC file. This - * attribute is defined if "initialized" is true. - */ - AudioFormat audio_format; - - /** - * The total number of frames in this song. The decoder - * plugin may initialize this attribute to override the value - * provided by libFLAC (e.g. for sub songs from a CUE sheet). - */ - FLAC__uint64 total_frames; - - /** - * The number of the first frame in this song. This is only - * non-zero if playing sub songs from a CUE sheet. - */ - FLAC__uint64 first_frame; - - /** - * The number of the next frame which is going to be decoded. - */ - FLAC__uint64 next_frame; - - FLAC__uint64 position; - - Decoder &decoder; - InputStream &input_stream; - - Tag tag; - - flac_data(Decoder &decoder, InputStream &input_stream); -}; - -void flac_metadata_common_cb(const FLAC__StreamMetadata * block, - struct flac_data *data); - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[], - FLAC__uint64 nbytes); - -#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx deleted file mode 100644 index 1b5734434..000000000 --- a/src/decoder/FlacDecoderPlugin.cxx +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FlacDecoderPlugin.h" -#include "FlacDomain.hxx" -#include "FlacCommon.hxx" -#include "FlacMetadata.hxx" -#include "OggCodec.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> - -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -#error libFLAC is too old -#endif - -static void flacPrintErroredState(FLAC__StreamDecoderState state) -{ - 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: - case FLAC__STREAM_DECODER_SEEK_ERROR: - case FLAC__STREAM_DECODER_ABORTED: - case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__STREAM_DECODER_UNINITIALIZED: - break; - } - - LogError(flac_domain, FLAC__StreamDecoderStateString[state]); -} - -static void flacMetadata(gcc_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 bool -flac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.Read(file)) { - FormatDebug(flac_domain, - "Failed to read FLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -static bool -flac_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.Read(is)) { - FormatDebug(flac_domain, - "Failed to read FLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -/** - * Some glue code around FLAC__stream_decoder_new(). - */ -static FLAC__StreamDecoder * -flac_decoder_new(void) -{ - FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == nullptr) { - LogError(flac_domain, - "FLAC__stream_decoder_new() failed"); - return nullptr; - } - - if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - LogDebug(flac_domain, - "FLAC__stream_decoder_set_metadata_respond() has failed"); - - return sd; -} - -static bool -flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, - FLAC__uint64 duration) -{ - data->total_frames = duration; - - if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { - LogWarning(flac_domain, "problem reading metadata"); - return false; - } - - if (data->initialized) { - /* done */ - decoder_initialized(data->decoder, data->audio_format, - data->input_stream.seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); - return true; - } - - if (data->input_stream.seekable) - /* allow the workaround below only for nonseekable - streams*/ - return false; - - /* no stream_info packet found; try to initialize the decoder - from the first frame header */ - FLAC__stream_decoder_process_single(sd); - return data->initialized; -} - -static void -flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, - FLAC__uint64 t_start, FLAC__uint64 t_end) -{ - Decoder &decoder = data->decoder; - - data->first_frame = t_start; - - while (true) { - DecoderCommand cmd; - if (!data->tag.IsEmpty()) { - cmd = decoder_tag(data->decoder, data->input_stream, - std::move(data->tag)); - data->tag.Clear(); - } else - cmd = decoder_get_command(decoder); - - if (cmd == DecoderCommand::SEEK) { - FLAC__uint64 seek_sample = t_start + - decoder_seek_where(decoder) * - data->audio_format.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 == DecoderCommand::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) && - decoder_get_command(decoder) == DecoderCommand::NONE) { - /* a failure that was not triggered by a - decoder command */ - flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); - break; - } - } -} - -static FLAC__StreamDecoderInitStatus -stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ - return FLAC__stream_decoder_init_ogg_stream(flac_dec, - FlacInput::Read, - FlacInput::Seek, - FlacInput::Tell, - FlacInput::Length, - FlacInput::Eof, - flac_write_cb, - flacMetadata, - FlacInput::Error, - data); -} - -static FLAC__StreamDecoderInitStatus -stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ - return FLAC__stream_decoder_init_stream(flac_dec, - FlacInput::Read, - FlacInput::Seek, - FlacInput::Tell, - FlacInput::Length, - FlacInput::Eof, - flac_write_cb, - flacMetadata, - FlacInput::Error, - data); -} - -static FLAC__StreamDecoderInitStatus -stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) -{ - return is_ogg - ? stream_init_oggflac(flac_dec, data) - : stream_init_flac(flac_dec, data); -} - -static void -flac_decode_internal(Decoder &decoder, - InputStream &input_stream, - bool is_ogg) -{ - FLAC__StreamDecoder *flac_dec; - - flac_dec = flac_decoder_new(); - if (flac_dec == nullptr) - return; - - struct flac_data data(decoder, input_stream); - - FLAC__StreamDecoderInitStatus status = - stream_init(flac_dec, &data, is_ogg); - if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - FLAC__stream_decoder_delete(flac_dec); - LogWarning(flac_domain, - FLAC__StreamDecoderInitStatusString[status]); - return; - } - - if (!flac_decoder_initialize(&data, flac_dec, 0)) { - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); - return; - } - - flac_decoder_loop(&data, flac_dec, 0, 0); - - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); -} - -static void -flac_decode(Decoder &decoder, InputStream &input_stream) -{ - flac_decode_internal(decoder, input_stream, false); -} - -static bool -oggflac_init(gcc_unused const config_param ¶m) -{ - return !!FLAC_API_SUPPORTS_OGG_FLAC; -} - -static bool -oggflac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.ReadOgg(file)) { - FormatDebug(flac_domain, - "Failed to read OggFLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -static bool -oggflac_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.ReadOgg(is)) { - FormatDebug(flac_domain, - "Failed to read OggFLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -static void -oggflac_decode(Decoder &decoder, InputStream &input_stream) -{ - if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_FLAC) - return; - - /* rewind the stream, because ogg_codec_detect() has - moved it */ - input_stream.LockRewind(IgnoreError()); - - flac_decode_internal(decoder, input_stream, true); -} - -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", - nullptr -}; - -const struct DecoderPlugin oggflac_decoder_plugin = { - "oggflac", - oggflac_init, - nullptr, - oggflac_decode, - nullptr, - oggflac_scan_file, - oggflac_scan_stream, - nullptr, - oggflac_suffixes, - oggflac_mime_types, -}; - -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", - nullptr -}; - -const struct DecoderPlugin flac_decoder_plugin = { - "flac", - nullptr, - nullptr, - flac_decode, - nullptr, - flac_scan_file, - flac_scan_stream, - nullptr, - flac_suffixes, - flac_mime_types, -}; diff --git a/src/decoder/FlacDecoderPlugin.h b/src/decoder/FlacDecoderPlugin.h deleted file mode 100644 index 936423fbf..000000000 --- a/src/decoder/FlacDecoderPlugin.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 DecoderPlugin flac_decoder_plugin; -extern const struct DecoderPlugin oggflac_decoder_plugin; - -#endif diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/FlacDomain.cxx deleted file mode 100644 index 5858004de..000000000 --- a/src/decoder/FlacDomain.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FlacDomain.hxx" -#include "util/Domain.hxx" - -const Domain flac_domain("flac"); diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/FlacDomain.hxx deleted file mode 100644 index cf357332f..000000000 --- a/src/decoder/FlacDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FLAC_DOMAIN_HXX -#define MPD_FLAC_DOMAIN_HXX - -#include "check.h" - -extern const class Domain flac_domain; - -#endif diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/FlacIOHandle.cxx deleted file mode 100644 index b471ecf64..000000000 --- a/src/decoder/FlacIOHandle.cxx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FlacIOHandle.hxx" -#include "util/Error.hxx" -#include "Compiler.h" - -#include <errno.h> - -static size_t -FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - uint8_t *const p0 = (uint8_t *)ptr, *p = p0, - *const end = p0 + size * nmemb; - - /* libFLAC is very picky about short reads, and expects the IO - callback to fill the whole buffer (undocumented!) */ - - Error error; - while (p < end) { - size_t nbytes = is->LockRead(p, end - p, error); - if (nbytes == 0) { - if (!error.IsDefined()) - /* end of file */ - break; - - if (error.IsDomain(errno_domain)) - errno = error.GetCode(); - else - /* just some random non-zero - errno value */ - errno = EINVAL; - return 0; - } - - p += nbytes; - } - - /* libFLAC expects a clean errno after returning from the IO - callbacks (undocumented!) */ - errno = 0; - return (p - p0) / size; -} - -static int -FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) -{ - InputStream *is = (InputStream *)handle; - - Error error; - return is->LockSeek(offset, whence, error) ? 0 : -1; -} - -static FLAC__int64 -FlacIOTell(FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - return is->offset; -} - -static int -FlacIOEof(FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - return is->LockIsEOF(); -} - -static int -FlacIOClose(gcc_unused FLAC__IOHandle handle) -{ - /* no-op because the libFLAC caller is repsonsible for closing - the #InputStream */ - - return 0; -} - -const FLAC__IOCallbacks flac_io_callbacks = { - FlacIORead, - nullptr, - nullptr, - nullptr, - FlacIOEof, - FlacIOClose, -}; - -const FLAC__IOCallbacks flac_io_callbacks_seekable = { - FlacIORead, - nullptr, - FlacIOSeek, - FlacIOTell, - FlacIOEof, - FlacIOClose, -}; diff --git a/src/decoder/FlacIOHandle.hxx b/src/decoder/FlacIOHandle.hxx deleted file mode 100644 index b6e563fa3..000000000 --- a/src/decoder/FlacIOHandle.hxx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FLAC_IO_HANDLE_HXX -#define MPD_FLAC_IO_HANDLE_HXX - -#include "Compiler.h" -#include "InputStream.hxx" - -#include <FLAC/callback.h> - -extern const FLAC__IOCallbacks flac_io_callbacks; -extern const FLAC__IOCallbacks flac_io_callbacks_seekable; - -static inline FLAC__IOHandle -ToFlacIOHandle(InputStream &is) -{ - return (FLAC__IOHandle)&is; -} - -static inline const FLAC__IOCallbacks & -GetFlacIOCallbacks(const InputStream &is) -{ - return is.seekable - ? flac_io_callbacks_seekable - : flac_io_callbacks; -} - -#endif diff --git a/src/decoder/FlacInput.cxx b/src/decoder/FlacInput.cxx deleted file mode 100644 index ce193101d..000000000 --- a/src/decoder/FlacInput.cxx +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FlacInput.hxx" -#include "FlacDomain.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "Log.hxx" -#include "Compiler.h" - -FLAC__StreamDecoderReadStatus -FlacInput::Read(FLAC__byte buffer[], size_t *bytes) -{ - size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes); - *bytes = r; - - if (r == 0) { - if (input_stream.LockIsEOF() || - (decoder != nullptr && - decoder_get_command(*decoder) != DecoderCommand::NONE)) - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -} - -FLAC__StreamDecoderSeekStatus -FlacInput::Seek(FLAC__uint64 absolute_byte_offset) -{ - if (!input_stream.seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - ::Error error; - if (!input_stream.LockSeek(absolute_byte_offset, SEEK_SET, error)) { - LogError(error); - return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; - } - - return FLAC__STREAM_DECODER_SEEK_STATUS_OK; -} - -FLAC__StreamDecoderTellStatus -FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) -{ - if (!input_stream.seekable) - return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; - - *absolute_byte_offset = (FLAC__uint64)input_stream.offset; - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -} - -FLAC__StreamDecoderLengthStatus -FlacInput::Length(FLAC__uint64 *stream_length) -{ - if (input_stream.size < 0) - return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - - *stream_length = (FLAC__uint64)input_stream.size; - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -} - -FLAC__bool -FlacInput::Eof() -{ - return (decoder != nullptr && - decoder_get_command(*decoder) != DecoderCommand::NONE && - decoder_get_command(*decoder) != DecoderCommand::SEEK) || - input_stream.LockIsEOF(); -} - -void -FlacInput::Error(FLAC__StreamDecoderErrorStatus status) -{ - if (decoder == nullptr || - decoder_get_command(*decoder) != DecoderCommand::STOP) - LogWarning(flac_domain, - FLAC__StreamDecoderErrorStatusString[status]); -} - -FLAC__StreamDecoderReadStatus -FlacInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__byte buffer[], size_t *bytes, - void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Read(buffer, bytes); -} - -FLAC__StreamDecoderSeekStatus -FlacInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 absolute_byte_offset, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Seek(absolute_byte_offset); -} - -FLAC__StreamDecoderTellStatus -FlacInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *absolute_byte_offset, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Tell(absolute_byte_offset); -} - -FLAC__StreamDecoderLengthStatus -FlacInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *stream_length, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Length(stream_length); -} - -FLAC__bool -FlacInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder, - void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Eof(); -} - -void -FlacInput::Error(gcc_unused const FLAC__StreamDecoder *decoder, - FLAC__StreamDecoderErrorStatus status, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - i->Error(status); -} - diff --git a/src/decoder/FlacInput.hxx b/src/decoder/FlacInput.hxx deleted file mode 100644 index ddd5649f8..000000000 --- a/src/decoder/FlacInput.hxx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FLAC_INPUT_HXX -#define MPD_FLAC_INPUT_HXX - -#include <FLAC/stream_decoder.h> - -struct Decoder; -struct InputStream; - -/** - * This class wraps an #InputStream in libFLAC stream decoder - * callbacks. - */ -class FlacInput { - Decoder *const decoder; - - InputStream &input_stream; - -public: - FlacInput(InputStream &_input_stream, - Decoder *_decoder=nullptr) - :decoder(_decoder), input_stream(_input_stream) {} - -protected: - FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); - FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); - FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); - FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); - FLAC__bool Eof(); - void Error(FLAC__StreamDecoderErrorStatus status); - -public: - static FLAC__StreamDecoderReadStatus - Read(const FLAC__StreamDecoder *flac_decoder, - FLAC__byte buffer[], size_t *bytes, void *client_data); - - static FLAC__StreamDecoderSeekStatus - Seek(const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 absolute_byte_offset, void *client_data); - - static FLAC__StreamDecoderTellStatus - Tell(const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *absolute_byte_offset, void *client_data); - - static FLAC__StreamDecoderLengthStatus - Length(const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *stream_length, void *client_data); - - static FLAC__bool - Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); - - static void - Error(const FLAC__StreamDecoder *decoder, - FLAC__StreamDecoderErrorStatus status, void *client_data); -}; - -#endif diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/FlacMetadata.cxx deleted file mode 100644 index 17cc4cd8d..000000000 --- a/src/decoder/FlacMetadata.cxx +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FlacMetadata.hxx" -#include "XiphTags.hxx" -#include "MixRampInfo.hxx" -#include "tag/Tag.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagTable.hxx" -#include "tag/TagBuilder.hxx" -#include "ReplayGainInfo.hxx" -#include "util/ASCII.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static bool -flac_find_float_comment(const FLAC__StreamMetadata *block, - const char *cmnt, float *fl) -{ - int offset; - size_t pos; - int len; - unsigned char tmp, *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return false; - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return false; - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - tmp = p[len]; - p[len] = '\0'; - *fl = (float)atof((char *)p); - p[len] = tmp; - - return true; -} - -bool -flac_parse_replay_gain(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block) -{ - rgi.Clear(); - - bool found = false; - if (flac_find_float_comment(block, "replaygain_album_gain", - &rgi.tuples[REPLAY_GAIN_ALBUM].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_album_peak", - &rgi.tuples[REPLAY_GAIN_ALBUM].peak)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_gain", - &rgi.tuples[REPLAY_GAIN_TRACK].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_peak", - &rgi.tuples[REPLAY_GAIN_TRACK].peak)) - found = true; - - return found; -} - -gcc_pure -static std::string -flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt) -{ - int offset; - size_t pos; - int len; - const unsigned char *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return std::string(); - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return std::string(); - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - return std::string((const char *)p, len); -} - -MixRampInfo -flac_parse_mixramp(const FLAC__StreamMetadata *block) -{ - MixRampInfo mix_ramp; - mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start")); - mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end")); - return mix_ramp; -} - -/** - * Checks if the specified name matches the entry's name, and if yes, - * returns the comment value (not null-temrinated). - */ -static const char * -flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, size_t *length_r) -{ - size_t name_length = strlen(name); - const char *comment = (const char*)entry->entry; - - if (entry->length <= name_length || - !StringEqualsCaseASCII(comment, name, name_length)) - return nullptr; - - if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; - return comment + name_length + 1; - } - - return nullptr; -} - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, TagType tag_type, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - size_t value_length; - - value = flac_comment_value(entry, name, &value_length); - if (value != nullptr) { - char *p = g_strndup(value, value_length); - tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); - g_free(p); - return true; - } - - return false; -} - -static void -flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != nullptr) { - char *name = g_strdup((const char*)entry->entry); - char *value = strchr(name, '='); - - if (value != nullptr && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) - if (flac_copy_comment(entry, i->name, i->type, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (flac_copy_comment(entry, - tag_item_names[i], (TagType)i, - handler, handler_ctx)) - return; -} - -static void -flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < comment->num_comments; ++i) - flac_scan_comment(&comment->comments[i], - handler, handler_ctx); -} - -void -flac_scan_metadata(const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx) -{ - switch (block->type) { - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_scan_comments(&block->data.vorbis_comment, - handler, handler_ctx); - break; - - case FLAC__METADATA_TYPE_STREAMINFO: - if (block->data.stream_info.sample_rate > 0) - tag_handler_invoke_duration(handler, handler_ctx, - flac_duration(&block->data.stream_info)); - break; - - default: - break; - } -} - -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment) -{ - TagBuilder tag_builder; - flac_scan_comments(comment, &add_tag_handler, &tag_builder); - tag_builder.Commit(tag); -} - -void -FlacMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) -{ - FLACMetadataIterator iterator(*this); - - do { - FLAC__StreamMetadata *block = iterator.GetBlock(); - if (block == nullptr) - break; - - flac_scan_metadata(block, handler, handler_ctx); - } while (iterator.Next()); -} diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/FlacMetadata.hxx deleted file mode 100644 index 96c61b8e6..000000000 --- a/src/decoder/FlacMetadata.hxx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FLAC_METADATA_H -#define MPD_FLAC_METADATA_H - -#include "Compiler.h" -#include "FlacIOHandle.hxx" - -#include <FLAC/metadata.h> - -#include <assert.h> - -class MixRampInfo; - -class FlacMetadataChain { - FLAC__Metadata_Chain *chain; - -public: - FlacMetadataChain():chain(::FLAC__metadata_chain_new()) {} - - ~FlacMetadataChain() { - ::FLAC__metadata_chain_delete(chain); - } - - explicit operator FLAC__Metadata_Chain *() { - return chain; - } - - bool Read(const char *path) { - return ::FLAC__metadata_chain_read(chain, path); - } - - bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { - return ::FLAC__metadata_chain_read_with_callbacks(chain, - handle, - callbacks); - } - - bool Read(InputStream &is) { - return Read(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); - } - - bool ReadOgg(const char *path) { - return ::FLAC__metadata_chain_read_ogg(chain, path); - } - - bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { - return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain, - handle, - callbacks); - } - - bool ReadOgg(InputStream &is) { - return ReadOgg(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); - } - - gcc_pure - FLAC__Metadata_ChainStatus GetStatus() const { - return ::FLAC__metadata_chain_status(chain); - } - - gcc_pure - const char *GetStatusString() const { - return FLAC__Metadata_ChainStatusString[GetStatus()]; - } - - void Scan(const struct tag_handler *handler, void *handler_ctx); -}; - -class FLACMetadataIterator { - FLAC__Metadata_Iterator *iterator; - -public: - FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {} - - FLACMetadataIterator(FlacMetadataChain &chain) - :iterator(::FLAC__metadata_iterator_new()) { - ::FLAC__metadata_iterator_init(iterator, - (FLAC__Metadata_Chain *)chain); - } - - ~FLACMetadataIterator() { - ::FLAC__metadata_iterator_delete(iterator); - } - - bool Next() { - return ::FLAC__metadata_iterator_next(iterator); - } - - gcc_pure - FLAC__StreamMetadata *GetBlock() { - return ::FLAC__metadata_iterator_get_block(iterator); - } -}; - -struct tag_handler; -struct Tag; -struct ReplayGainInfo; - -static inline unsigned -flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - assert(stream_info->sample_rate > 0); - - return (stream_info->total_samples + stream_info->sample_rate - 1) / - stream_info->sample_rate; -} - -bool -flac_parse_replay_gain(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block); - -MixRampInfo -flac_parse_mixramp(const FLAC__StreamMetadata *block); - -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment); - -void -flac_scan_metadata(const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/FlacPcm.cxx deleted file mode 100644 index 569879371..000000000 --- a/src/decoder/FlacPcm.cxx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FlacPcm.hxx" - -#include <assert.h> - -static void flac_convert_stereo16(int16_t *dest, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - for (; position < end; ++position) { - *dest++ = buf[0][position]; - *dest++ = buf[1][position]; - } -} - -static void -flac_convert_16(int16_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -/** - * Note: this function also handles 24 bit files! - */ -static void -flac_convert_32(int32_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -static void -flac_convert_8(int8_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -void -flac_convert(void *dest, - unsigned int num_channels, SampleFormat sample_format, - const FLAC__int32 *const buf[], - unsigned int position, unsigned int end) -{ - switch (sample_format) { - case SampleFormat::S16: - if (num_channels == 2) - flac_convert_stereo16((int16_t*)dest, buf, - position, end); - else - flac_convert_16((int16_t*)dest, num_channels, buf, - position, end); - break; - - case SampleFormat::S24_P32: - case SampleFormat::S32: - flac_convert_32((int32_t*)dest, num_channels, buf, - position, end); - break; - - case SampleFormat::S8: - flac_convert_8((int8_t*)dest, num_channels, buf, - position, end); - break; - - case SampleFormat::FLOAT: - case SampleFormat::DSD: - case SampleFormat::UNDEFINED: - assert(false); - gcc_unreachable(); - } -} diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/FlacPcm.hxx deleted file mode 100644 index 6cb2d5062..000000000 --- a/src/decoder/FlacPcm.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FLAC_PCM_HXX -#define MPD_FLAC_PCM_HXX - -#include "AudioFormat.hxx" - -#include <FLAC/ordinals.h> - -void -flac_convert(void *dest, - unsigned int num_channels, SampleFormat sample_format, - const FLAC__int32 *const buf[], - unsigned int position, unsigned int end); - -#endif diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/FluidsynthDecoderPlugin.cxx deleted file mode 100644 index fa946f219..000000000 --- a/src/decoder/FluidsynthDecoderPlugin.cxx +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "FluidsynthDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <fluidsynth.h> - -static constexpr Domain fluidsynth_domain("fluidsynth"); - -static unsigned sample_rate; -static const char *soundfont_path; - -/** - * Convert a fluidsynth log level to a GLib log level. - */ -static LogLevel -fluidsynth_level_to_mpd(enum fluid_log_level level) -{ - switch (level) { - case FLUID_PANIC: - case FLUID_ERR: - return LogLevel::ERROR; - - case FLUID_WARN: - return LogLevel::WARNING; - - case FLUID_INFO: - return LogLevel::INFO; - - case FLUID_DBG: - case LAST_LOG_LEVEL: - return LogLevel::DEBUG; - } - - /* invalid fluidsynth log level */ - return LogLevel::INFO; -} - -/** - * The fluidsynth logging callback. It forwards messages to the GLib - * logging library. - */ -static void -fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data) -{ - Log(fluidsynth_domain, - fluidsynth_level_to_mpd(fluid_log_level(level)), - message); -} - -static bool -fluidsynth_init(const config_param ¶m) -{ - Error error; - - sample_rate = param.GetBlockValue("sample_rate", 48000u); - if (!audio_check_sample_rate(sample_rate, error)) { - LogError(error); - return false; - } - - soundfont_path = param.GetBlockValue("soundfont", - "/usr/share/sounds/sf2/FluidR3_GM.sf2"); - - fluid_set_log_function(LAST_LOG_LEVEL, - fluidsynth_mpd_log_function, nullptr); - - return true; -} - -static void -fluidsynth_file_decode(Decoder &decoder, const char *path_fs) -{ - char setting_sample_rate[] = "synth.sample-rate"; - /* - char setting_verbose[] = "synth.verbose"; - char setting_yes[] = "yes"; - */ - fluid_settings_t *settings; - fluid_synth_t *synth; - fluid_player_t *player; - int ret; - - /* set up fluid settings */ - - settings = new_fluid_settings(); - if (settings == nullptr) - return; - - fluid_settings_setnum(settings, setting_sample_rate, sample_rate); - - /* - fluid_settings_setstr(settings, setting_verbose, setting_yes); - */ - - /* create the fluid synth */ - - synth = new_fluid_synth(settings); - if (synth == nullptr) { - delete_fluid_settings(settings); - return; - } - - ret = fluid_synth_sfload(synth, soundfont_path, true); - if (ret < 0) { - LogWarning(fluidsynth_domain, "fluid_synth_sfload() failed"); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* create the fluid player */ - - player = new_fluid_player(synth); - if (player == nullptr) { - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - ret = fluid_player_add(player, path_fs); - if (ret != 0) { - LogWarning(fluidsynth_domain, "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) { - LogWarning(fluidsynth_domain, "fluid_player_play() failed"); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* initialization complete - announce the audio format to the - MPD core */ - - const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); - decoder_initialized(decoder, audio_format, false, -1); - - DecoderCommand cmd; - while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { - int16_t buffer[2048]; - const unsigned max_frames = ARRAY_SIZE(buffer) / 2; - - /* 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); - if (ret != 0) - break; - - cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer), - 0); - if (cmd != DecoderCommand::NONE) - break; - } - - /* clean up */ - - fluid_player_stop(player); - fluid_player_join(player); - - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); -} - -static bool -fluidsynth_scan_file(const char *file, - gcc_unused const struct tag_handler *handler, - gcc_unused void *handler_ctx) -{ - return fluid_is_midifile(file); -} - -static const char *const fluidsynth_suffixes[] = { - "mid", - nullptr -}; - -const struct DecoderPlugin fluidsynth_decoder_plugin = { - "fluidsynth", - fluidsynth_init, - nullptr, - nullptr, - fluidsynth_file_decode, - fluidsynth_scan_file, - nullptr, - nullptr, - fluidsynth_suffixes, - nullptr, -}; diff --git a/src/decoder/FluidsynthDecoderPlugin.hxx b/src/decoder/FluidsynthDecoderPlugin.hxx deleted file mode 100644 index 9771898a5..000000000 --- a/src/decoder/FluidsynthDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FLUIDSYNTH_HXX -#define MPD_DECODER_FLUIDSYNTH_HXX - -extern const struct DecoderPlugin fluidsynth_decoder_plugin; - -#endif diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx deleted file mode 100644 index 9c9b19478..000000000 --- a/src/decoder/GmeDecoderPlugin.cxx +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "GmeDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/FormatString.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#include <gme/gme.h> - -#define SUBTUNE_PREFIX "tune_" - -static constexpr Domain gme_domain("gme"); - -static constexpr unsigned GME_SAMPLE_RATE = 44100; -static constexpr unsigned GME_CHANNELS = 2; -static constexpr unsigned GME_BUFFER_FRAMES = 2048; -static constexpr unsigned GME_BUFFER_SAMPLES = - GME_BUFFER_FRAMES * GME_CHANNELS; - -/** - * returns the file path stripped of any /tune_xxx.* subtune - * suffix - */ -static char * -get_container_name(const char *path_fs) -{ - const char *subtune_suffix = uri_get_suffix(path_fs); - char *path_container = g_strdup(path_fs); - - char pat[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); - if (!g_pattern_match(path_with_subtune, - strlen(path_container), path_container, nullptr)) { - g_pattern_spec_free(path_with_subtune); - return path_container; - } - - char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); - if (ptr != nullptr) - *ptr='\0'; - - g_pattern_spec_free(path_with_subtune); - return path_container; -} - -/** - * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune - * is appended. - */ -static int -get_song_num(const char *path_fs) -{ - const char *subtune_suffix = uri_get_suffix(path_fs); - - char pat[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); - - if (g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, nullptr)) { - char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX); - g_pattern_spec_free(path_with_subtune); - if (!sub) - return 0; - - sub += strlen("/" SUBTUNE_PREFIX); - int song_num = strtol(sub, nullptr, 10); - - return song_num - 1; - } else { - g_pattern_spec_free(path_with_subtune); - return 0; - } -} - -static char * -gme_container_scan(const char *path_fs, const unsigned int tnum) -{ - Music_Emu *emu; - const char *gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return nullptr; - } - - const unsigned num_songs = gme_track_count(emu); - gme_delete(emu); - /* if it only contains a single tune, don't treat as container */ - if (num_songs < 2) - return nullptr; - - const char *subtune_suffix = uri_get_suffix(path_fs); - if (tnum <= num_songs){ - return FormatNew(SUBTUNE_PREFIX "%03u.%s", - tnum, subtune_suffix); - } else - return nullptr; -} - -static void -gme_file_decode(Decoder &decoder, const char *path_fs) -{ - char *path_container = get_container_name(path_fs); - - Music_Emu *emu; - const char *gme_err = - gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - g_free(path_container); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return; - } - - gme_info_t *ti; - const int song_num = get_song_num(path_fs); - gme_err = gme_track_info(emu, &ti, song_num); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - gme_delete(emu); - return; - } - - const float song_len = ti->length > 0 - ? ti->length / 1000.0 - : -1.0; - - /* initialize the MPD decoder */ - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE, - SampleFormat::S16, GME_CHANNELS, - error)) { - LogError(error); - gme_free_info(ti); - gme_delete(emu); - return; - } - - decoder_initialized(decoder, audio_format, true, song_len); - - gme_err = gme_start_track(emu, song_num); - if (gme_err != nullptr) - LogWarning(gme_domain, gme_err); - - if (ti->length > 0) - gme_set_fade(emu, ti->length); - - /* play */ - DecoderCommand cmd; - do { - short buf[GME_BUFFER_SAMPLES]; - gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return; - } - - cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0); - if (cmd == DecoderCommand::SEEK) { - float where = decoder_seek_where(decoder); - gme_err = gme_seek(emu, int(where * 1000)); - if (gme_err != nullptr) - LogWarning(gme_domain, gme_err); - decoder_command_finished(decoder); - } - - if (gme_track_ended(emu)) - break; - } while (cmd != DecoderCommand::STOP); - - gme_free_info(ti); - gme_delete(emu); -} - -static bool -gme_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - char *path_container = get_container_name(path_fs); - - Music_Emu *emu; - const char *gme_err = - gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - g_free(path_container); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return false; - } - - const int song_num = get_song_num(path_fs); - - gme_info_t *ti; - gme_err = gme_track_info(emu, &ti, song_num); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - gme_delete(emu); - return false; - } - - assert(ti != nullptr); - - if (ti->length > 0) - tag_handler_invoke_duration(handler, handler_ctx, - ti->length / 1000); - - if (ti->song != nullptr) { - if (gme_track_count(emu) > 1) { - /* start numbering subtunes from 1 */ - char tag_title[1024]; - snprintf(tag_title, sizeof(tag_title), - "%s (%d/%d)", - ti->song, song_num + 1, - gme_track_count(emu)); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, tag_title); - } else - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, ti->song); - } - - if (ti->author != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, ti->author); - - if (ti->game != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ALBUM, ti->game); - - if (ti->comment != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_COMMENT, ti->comment); - - if (ti->copyright != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, ti->copyright); - - gme_free_info(ti); - gme_delete(emu); - - return true; -} - -static const char *const gme_suffixes[] = { - "ay", "gbs", "gym", "hes", "kss", "nsf", - "nsfe", "sap", "spc", "vgm", "vgz", - nullptr -}; - -extern const struct DecoderPlugin gme_decoder_plugin; -const struct DecoderPlugin gme_decoder_plugin = { - "gme", - nullptr, - nullptr, - nullptr, - gme_file_decode, - gme_scan_file, - nullptr, - gme_container_scan, - gme_suffixes, - nullptr, -}; diff --git a/src/decoder/GmeDecoderPlugin.hxx b/src/decoder/GmeDecoderPlugin.hxx deleted file mode 100644 index e46dc766a..000000000 --- a/src/decoder/GmeDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_GME_HXX -#define MPD_DECODER_GME_HXX - -extern const struct DecoderPlugin gme_decoder_plugin; - -#endif diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx deleted file mode 100644 index 6f619b34b..000000000 --- a/src/decoder/MadDecoderPlugin.cxx +++ /dev/null @@ -1,1155 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "MadDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "ConfigGlobal.hxx" -#include "tag/TagId3.hxx" -#include "tag/TagRva2.hxx" -#include "tag/TagHandler.hxx" -#include "CheckAudioFormat.hxx" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <glib.h> -#include <mad.h> - -#ifdef HAVE_ID3TAG -#include <id3tag.h> -#endif - -#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 constexpr Domain mad_domain("mad"); - -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 (gcc_unlikely(sample > MAX)) - sample = MAX; - else if (gcc_unlikely(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(gcc_unused const config_param ¶m) -{ - gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, - DEFAULT_GAPLESS_MP3_PLAYBACK); - return true; -} - -#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 - -struct MadDecoder { - 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; - Decoder *const decoder; - InputStream &input_stream; - enum mad_layer layer; - - MadDecoder(Decoder *decoder, InputStream &input_stream); - ~MadDecoder(); - - bool Seek(long offset); - bool FillBuffer(); - void ParseId3(size_t tagsize, Tag **mpd_tag); - enum mp3_action DecodeNextFrameHeader(Tag **tag); - enum mp3_action DecodeNextFrame(); - - gcc_pure - InputStream::offset_type ThisFrameOffset() const; - - gcc_pure - InputStream::offset_type RestIncludingThisFrame() const; - - /** - * Attempt to calulcate the length of the song from filesize - */ - void FileSizeToSongLength(); - - bool DecodeFirstFrame(Tag **tag); - - gcc_pure - long TimeToFrame(double t) const; - - void UpdateTimerNextFrame(); - - /** - * Sends the synthesized current frame via decoder_data(). - */ - DecoderCommand SendPCM(unsigned i, unsigned pcm_length); - - /** - * Synthesize the current frame and send it via - * decoder_data(). - */ - DecoderCommand SyncAndSend(); - - bool Read(); -}; - -MadDecoder::MadDecoder(Decoder *_decoder, - InputStream &_input_stream) - :mute_frame(MUTEFRAME_NONE), - frame_offsets(nullptr), - times(nullptr), - highest_frame(0), max_frames(0), current_frame(0), - drop_start_frames(0), drop_end_frames(0), - drop_start_samples(0), drop_end_samples(0), - found_replay_gain(false), found_xing(false), - found_first_frame(false), decoded_first_frame(false), - decoder(_decoder), input_stream(_input_stream), - layer(mad_layer(0)) -{ - mad_stream_init(&stream); - mad_stream_options(&stream, MAD_OPTION_IGNORECRC); - mad_frame_init(&frame); - mad_synth_init(&synth); - mad_timer_reset(&timer); -} - -inline bool -MadDecoder::Seek(long offset) -{ - Error error; - if (!input_stream.LockSeek(offset, SEEK_SET, error)) - return false; - - mad_stream_buffer(&stream, input_buffer, 0); - stream.error = MAD_ERROR_NONE; - - return true; -} - -inline bool -MadDecoder::FillBuffer() -{ - size_t remaining, length; - unsigned char *dest; - - if (stream.next_frame != nullptr) { - remaining = stream.bufend - stream.next_frame; - memmove(input_buffer, stream.next_frame, remaining); - dest = input_buffer + remaining; - length = READ_BUFFER_SIZE - remaining; - } else { - remaining = 0; - length = READ_BUFFER_SIZE; - dest = 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(decoder, input_stream, dest, length); - if (length == 0) - return false; - - mad_stream_buffer(&stream, input_buffer, length + remaining); - stream.error = MAD_ERROR_NONE; - - return true; -} - -#ifdef HAVE_ID3TAG -static bool -parse_id3_replay_gain_info(ReplayGainInfo &rgi, - struct id3_tag *tag) -{ - int i; - char *key; - char *value; - struct id3_frame *frame; - bool found = false; - - rgi.Clear(); - - 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 (StringEqualsCaseASCII(key, "replaygain_track_gain")) { - rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) { - rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) { - rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) { - rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(value); - found = true; - } - - free(key); - free(value); - } - - return found || - /* fall back on RVA2 if no replaygain tags found */ - tag_rva2_parse(tag, rgi); -} -#endif - -#ifdef HAVE_ID3TAG -gcc_pure -static MixRampInfo -parse_id3_mixramp(struct id3_tag *tag) -{ - int i; - char *key; - char *value; - struct id3_frame *frame; - - MixRampInfo result; - - 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 (StringEqualsCaseASCII(key, "mixramp_start")) { - result.SetStart(value); - } else if (StringEqualsCaseASCII(key, "mixramp_end")) { - result.SetEnd(value); - } - - free(key); - free(value); - } - - return result; -} -#endif - -inline void -MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) -{ -#ifdef HAVE_ID3TAG - struct id3_tag *id3_tag = nullptr; - id3_length_t count; - id3_byte_t const *id3_data; - id3_byte_t *allocated = nullptr; - - count = stream.bufend - stream.this_frame; - - if (tagsize <= count) { - id3_data = stream.this_frame; - mad_stream_skip(&(stream), tagsize); - } else { - allocated = (id3_byte_t *)g_malloc(tagsize); - memcpy(allocated, stream.this_frame, count); - mad_stream_skip(&(stream), count); - - if (!decoder_read_full(decoder, input_stream, - allocated + count, tagsize - count)) { - LogDebug(mad_domain, "error parsing ID3 tag"); - g_free(allocated); - return; - } - - id3_data = allocated; - } - - id3_tag = id3_tag_parse(id3_data, tagsize); - if (id3_tag == nullptr) { - g_free(allocated); - return; - } - - if (mpd_tag) { - Tag *tmp_tag = tag_id3_import(id3_tag); - if (tmp_tag != nullptr) { - delete *mpd_tag; - *mpd_tag = tmp_tag; - } - } - - if (decoder != nullptr) { - ReplayGainInfo rgi; - - if (parse_id3_replay_gain_info(rgi, id3_tag)) { - decoder_replay_gain(*decoder, &rgi); - found_replay_gain = true; - } - - decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag)); - } - - 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. */ - - size_t count = stream.bufend - stream.this_frame; - - if (tagsize <= count) { - mad_stream_skip(&stream, tagsize); - } else { - mad_stream_skip(&stream, count); - decoder_skip(decoder, input_stream, tagsize - count); - } -#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 length - * of the ID3 frame. - */ -static signed long -id3_tag_query(const void *p0, size_t length) -{ - const char *p = (const char *)p0; - - return length >= 10 && memcmp(p, "ID3", 3) == 0 - ? (p[8] << 7) + p[9] + 10 - : 0; -} -#endif /* !HAVE_ID3TAG */ - -enum mp3_action -MadDecoder::DecodeNextFrameHeader(Tag **tag) -{ - if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && - !FillBuffer()) - return DECODE_BREAK; - - if (mad_header_decode(&frame.header, &stream)) { - if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) { - signed long tagsize = id3_tag_query(stream.this_frame, - stream.bufend - - stream.this_frame); - - if (tagsize > 0) { - if (tag && !(*tag)) { - ParseId3((size_t)tagsize, tag); - } else { - mad_stream_skip(&stream, tagsize); - } - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE(stream.error)) { - return DECODE_SKIP; - } else { - if (stream.error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - FormatWarning(mad_domain, - "unrecoverable frame level error: %s", - mad_stream_errorstr(&stream)); - return DECODE_BREAK; - } - } - } - - enum mad_layer new_layer = frame.header.layer; - if (layer == (mad_layer)0) { - if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) { - /* Only layer 2 and 3 have been tested to work */ - return DECODE_SKIP; - } - - layer = new_layer; - } else if (new_layer != layer) { - /* Don't decode frames with a different layer than the first */ - return DECODE_SKIP; - } - - return DECODE_OK; -} - -enum mp3_action -MadDecoder::DecodeNextFrame() -{ - if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && - !FillBuffer()) - return DECODE_BREAK; - - if (mad_frame_decode(&frame, &stream)) { - if (stream.error == MAD_ERROR_LOSTSYNC) { - signed long tagsize = id3_tag_query(stream.this_frame, - stream.bufend - - stream.this_frame); - if (tagsize > 0) { - mad_stream_skip(&stream, tagsize); - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE(stream.error)) { - return DECODE_SKIP; - } else { - if (stream.error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - FormatWarning(mad_domain, - "unrecoverable frame level error: %s", - mad_stream_errorstr(&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 lame_version { - unsigned major; - unsigned minor; -}; - -struct lame { - char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ - struct lame_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; - - FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")", - 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 */ - FormatDebug(mad_domain, "LAME peak found: %f", 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; - FormatDebug(mad_domain, "LAME track gain found: %f", - 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; - FormatDebug(mad_domain, "LAME album gain found: %f", - 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); - - FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i", - 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; -} - -inline InputStream::offset_type -MadDecoder::ThisFrameOffset() const -{ - auto offset = input_stream.GetOffset(); - - if (stream.this_frame != nullptr) - offset -= stream.bufend - stream.this_frame; - else - offset -= stream.bufend - stream.buffer; - - return offset; -} - -inline InputStream::offset_type -MadDecoder::RestIncludingThisFrame() const -{ - return input_stream.GetSize() - ThisFrameOffset(); -} - -inline void -MadDecoder::FileSizeToSongLength() -{ - InputStream::offset_type rest = RestIncludingThisFrame(); - - if (rest > 0) { - float frame_duration = mp3_frame_duration(&frame); - - total_time = (rest * 8.0) / frame.header.bitrate; - max_frames = total_time / frame_duration + FRAMES_CUSHION; - } else { - max_frames = FRAMES_CUSHION; - total_time = 0; - } -} - -inline bool -MadDecoder::DecodeFirstFrame(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 = DecodeNextFrameHeader(tag); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_SKIP) continue; - - do { - ret = DecodeNextFrame(); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_OK) break; - } - - ptr = stream.anc_ptr; - bitlen = stream.anc_bitlen; - - FileSizeToSongLength(); - - /* - * if an xing tag exists, use that! - */ - if (parse_xing(&xing, &ptr, &bitlen)) { - found_xing = true; - mute_frame = MUTEFRAME_SKIP; - - if ((xing.flags & XING_FRAMES) && xing.frames) { - mad_timer_t duration = frame.header.duration; - mad_timer_multiply(&duration, xing.frames); - total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; - max_frames = xing.frames; - } - - if (parse_lame(&lame, &ptr, &bitlen)) { - if (gapless_playback && input_stream.IsSeekable()) { - drop_start_samples = lame.encoder_delay + - DECODERDELAY; - drop_end_samples = lame.encoder_padding; - } - - /* Album gain isn't currently used. See comment in - * parse_lame() for details. -- jat */ - if (decoder != nullptr && !found_replay_gain && - lame.track_gain) { - ReplayGainInfo rgi; - rgi.Clear(); - rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; - rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak; - decoder_replay_gain(*decoder, &rgi); - } - } - } - - if (!max_frames) - return false; - - if (max_frames > 8 * 1024 * 1024) { - FormatWarning(mad_domain, - "mp3 file header indicates too many frames: %lu", - max_frames); - return false; - } - - frame_offsets = new long[max_frames]; - times = new mad_timer_t[max_frames]; - - return true; -} - -MadDecoder::~MadDecoder() -{ - mad_synth_finish(&synth); - mad_frame_finish(&frame); - mad_stream_finish(&stream); - - delete[] frame_offsets; - delete[] times; -} - -/* this is primarily used for getting total time for tags */ -static int -mad_decoder_total_file_time(InputStream &is) -{ - MadDecoder data(nullptr, is); - return data.DecodeFirstFrame(nullptr) - ? data.total_time + 0.5 - : -1; -} - -long -MadDecoder::TimeToFrame(double t) const -{ - unsigned long i; - - for (i = 0; i < highest_frame; ++i) { - double frame_time = - mad_timer_count(times[i], - MAD_UNITS_MILLISECONDS) / 1000.; - if (frame_time >= t) - break; - } - - return i; -} - -void -MadDecoder::UpdateTimerNextFrame() -{ - if (current_frame >= highest_frame) { - /* record this frame's properties in frame_offsets - (for seeking) and times */ - bit_rate = frame.header.bitrate; - - if (current_frame >= max_frames) - /* cap current_frame */ - current_frame = max_frames - 1; - else - highest_frame++; - - frame_offsets[current_frame] = ThisFrameOffset(); - - mad_timer_add(&timer, frame.header.duration); - times[current_frame] = timer; - } else - /* get the new timer value from "times" */ - timer = times[current_frame]; - - current_frame++; - elapsed_time = mad_timer_count(timer, MAD_UNITS_MILLISECONDS) / 1000.0; -} - -DecoderCommand -MadDecoder::SendPCM(unsigned i, unsigned pcm_length) -{ - unsigned max_samples; - - max_samples = sizeof(output_buffer) / - sizeof(output_buffer[0]) / - MAD_NCHANNELS(&frame.header); - - while (i < pcm_length) { - unsigned int num_samples = pcm_length - i; - if (num_samples > max_samples) - num_samples = max_samples; - - i += num_samples; - - mad_fixed_to_24_buffer(output_buffer, &synth, - i - num_samples, i, - MAD_NCHANNELS(&frame.header)); - num_samples *= MAD_NCHANNELS(&frame.header); - - auto cmd = decoder_data(*decoder, input_stream, output_buffer, - sizeof(output_buffer[0]) * num_samples, - bit_rate / 1000); - if (cmd != DecoderCommand::NONE) - return cmd; - } - - return DecoderCommand::NONE; -} - -inline DecoderCommand -MadDecoder::SyncAndSend() -{ - mad_synth_frame(&synth, &frame); - - if (!found_first_frame) { - unsigned int samples_per_frame = synth.pcm.length; - drop_start_frames = drop_start_samples / samples_per_frame; - drop_end_frames = drop_end_samples / samples_per_frame; - drop_start_samples = drop_start_samples % samples_per_frame; - drop_end_samples = drop_end_samples % samples_per_frame; - found_first_frame = true; - } - - if (drop_start_frames > 0) { - drop_start_frames--; - return DecoderCommand::NONE; - } else if ((drop_end_frames > 0) && - (current_frame == (max_frames + 1 - drop_end_frames))) { - /* stop decoding, effectively dropping all remaining - frames */ - return DecoderCommand::STOP; - } - - unsigned i = 0; - if (!decoded_first_frame) { - i = drop_start_samples; - decoded_first_frame = true; - } - - unsigned pcm_length = synth.pcm.length; - if (drop_end_samples && - (current_frame == max_frames - drop_end_frames)) { - if (drop_end_samples >= pcm_length) - pcm_length = 0; - else - pcm_length -= drop_end_samples; - } - - auto cmd = SendPCM(i, pcm_length); - if (cmd != DecoderCommand::NONE) - return cmd; - - if (drop_end_samples && - (current_frame == max_frames - drop_end_frames)) - /* stop decoding, effectively dropping - * all remaining samples */ - return DecoderCommand::STOP; - - return DecoderCommand::NONE; -} - -inline bool -MadDecoder::Read() -{ - enum mp3_action ret; - - UpdateTimerNextFrame(); - - switch (mute_frame) { - DecoderCommand cmd; - - case MUTEFRAME_SKIP: - mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_SEEK: - if (elapsed_time >= seek_where) - mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_NONE: - cmd = SyncAndSend(); - if (cmd == DecoderCommand::SEEK) { - unsigned long j; - - assert(input_stream.IsSeekable()); - - j = TimeToFrame(decoder_seek_where(*decoder)); - if (j < highest_frame) { - if (Seek(frame_offsets[j])) { - current_frame = j; - decoder_command_finished(*decoder); - } else - decoder_seek_error(*decoder); - } else { - seek_where = decoder_seek_where(*decoder); - mute_frame = MUTEFRAME_SEEK; - decoder_command_finished(*decoder); - } - } else if (cmd != DecoderCommand::NONE) - return false; - } - - while (true) { - bool skip = false; - - do { - Tag *tag = nullptr; - - ret = DecodeNextFrameHeader(&tag); - - if (tag != nullptr) { - decoder_tag(*decoder, input_stream, - std::move(*tag)); - delete tag; - } - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - else if (ret == DECODE_SKIP) - skip = true; - - if (mute_frame == MUTEFRAME_NONE) { - do { - ret = DecodeNextFrame(); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - } - - if (!skip && ret == DECODE_OK) - break; - } - - return ret != DECODE_BREAK; -} - -static void -mp3_decode(Decoder &decoder, InputStream &input_stream) -{ - MadDecoder data(&decoder, input_stream); - - Tag *tag = nullptr; - if (!data.DecodeFirstFrame(&tag)) { - delete tag; - - if (decoder_get_command(decoder) == DecoderCommand::NONE) - LogError(mad_domain, - "Input does not appear to be a mp3 bit stream"); - return; - } - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, - data.frame.header.samplerate, - SampleFormat::S24_P32, - MAD_NCHANNELS(&data.frame.header), - error)) { - LogError(error); - delete tag; - return; - } - - decoder_initialized(decoder, audio_format, - input_stream.IsSeekable(), - data.total_time); - - if (tag != nullptr) { - decoder_tag(decoder, input_stream, std::move(*tag)); - delete tag; - } - - while (data.Read()) {} -} - -static bool -mad_decoder_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - int total_time; - - total_time = mad_decoder_total_file_time(is); - if (total_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr }; -static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr }; - -const struct DecoderPlugin mad_decoder_plugin = { - "mad", - mp3_plugin_init, - nullptr, - mp3_decode, - nullptr, - nullptr, - mad_decoder_scan_stream, - nullptr, - mp3_suffixes, - mp3_mime_types, -}; diff --git a/src/decoder/MadDecoderPlugin.hxx b/src/decoder/MadDecoderPlugin.hxx deleted file mode 100644 index 450323670..000000000 --- a/src/decoder/MadDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_MAD_HXX -#define MPD_DECODER_MAD_HXX - -extern const struct DecoderPlugin mad_decoder_plugin; - -#endif diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx deleted file mode 100644 index 34381aafa..000000000 --- a/src/decoder/MikmodDecoderPlugin.cxx +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "MikmodDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "tag/TagHandler.hxx" -#include "system/FatalError.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <mikmod.h> -#include <assert.h> - -static constexpr Domain mikmod_domain("mikmod"); - -/* this is largely copied from alsaplayer */ - -static constexpr size_t 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 = { - nullptr, - drv_name, - drv_version, - 0, - 255, -#if (LIBMIKMOD_VERSION > 0x030106) - drv_alias, -#if (LIBMIKMOD_VERSION >= 0x030200) - nullptr, /* CmdLineHelp */ -#endif - nullptr, /* CommandLine */ -#endif - mikmod_mpd_is_present, - VC_SampleLoad, - VC_SampleUnload, - VC_SampleSpace, - VC_SampleLength, - mikmod_mpd_init, - mikmod_mpd_exit, - nullptr, - VC_SetNumVoices, - VC_PlayStart, - VC_PlayStop, - mikmod_mpd_update, - nullptr, - VC_VoiceSetVolume, - VC_VoiceGetVolume, - VC_VoiceSetFrequency, - VC_VoiceGetFrequency, - VC_VoiceSetPanning, - VC_VoiceGetPanning, - VC_VoicePlay, - VC_VoiceStop, - VC_VoiceStopped, - VC_VoiceGetPosition, - VC_VoiceRealVolume -}; - -static bool mikmod_loop; -static unsigned mikmod_sample_rate; - -static bool -mikmod_decoder_init(const config_param ¶m) -{ - static char params[] = ""; - - mikmod_loop = param.GetBlockValue("loop", false); - mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u); - if (!audio_valid_sample_rate(mikmod_sample_rate)) - FormatFatalError("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)) { - FormatError(mikmod_domain, - "Could not init MikMod: %s", - MikMod_strerror(MikMod_errno)); - return false; - } - - return true; -} - -static void -mikmod_decoder_finish(void) -{ - MikMod_Exit(); -} - -static void -mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs) -{ - /* deconstify the path because libmikmod wants a non-const - string pointer */ - char *const path2 = const_cast<char *>(path_fs); - - MODULE *handle; - int ret; - SBYTE buffer[MIKMOD_FRAME_SIZE]; - - handle = Player_Load(path2, 128, 0); - - if (handle == nullptr) { - FormatError(mikmod_domain, - "failed to open mod: %s", path_fs); - return; - } - - handle->loop = mikmod_loop; - - const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, false, 0); - - Player_Start(handle); - - DecoderCommand cmd = DecoderCommand::NONE; - while (cmd == DecoderCommand::NONE && Player_Active()) { - ret = VC_WriteBytes(buffer, sizeof(buffer)); - cmd = decoder_data(decoder, nullptr, buffer, ret, 0); - } - - Player_Stop(); - Player_Free(handle); -} - -static bool -mikmod_decoder_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - /* deconstify the path because libmikmod wants a non-const - string pointer */ - char *const path2 = const_cast<char *>(path_fs); - - MODULE *handle = Player_Load(path2, 128, 0); - - if (handle == nullptr) { - FormatDebug(mikmod_domain, - "Failed to open file: %s", path_fs); - return false; - } - - Player_Free(handle); - - char *title = Player_LoadTitle(path2); - if (title != nullptr) { - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, title); -#if (LIBMIKMOD_VERSION >= 0x030200) - MikMod_free(title); -#else - free(title); -#endif - } - - return true; -} - -static const char *const mikmod_decoder_suffixes[] = { - "amf", - "dsm", - "far", - "gdm", - "imf", - "it", - "med", - "mod", - "mtm", - "s3m", - "stm", - "stx", - "ult", - "uni", - "xm", - nullptr -}; - -const struct DecoderPlugin mikmod_decoder_plugin = { - "mikmod", - mikmod_decoder_init, - mikmod_decoder_finish, - nullptr, - mikmod_decoder_file_decode, - mikmod_decoder_scan_file, - nullptr, - nullptr, - mikmod_decoder_suffixes, - nullptr, -}; diff --git a/src/decoder/MikmodDecoderPlugin.hxx b/src/decoder/MikmodDecoderPlugin.hxx deleted file mode 100644 index d25c5f6e7..000000000 --- a/src/decoder/MikmodDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_MIKMOD_HXX -#define MPD_DECODER_MIKMOD_HXX - -extern const struct DecoderPlugin mikmod_decoder_plugin; - -#endif diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx deleted file mode 100644 index e75f5479c..000000000 --- a/src/decoder/ModplugDecoderPlugin.cxx +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "ModplugDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "tag/TagHandler.hxx" -#include "system/FatalError.hxx" -#include "util/WritableBuffer.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <libmodplug/modplug.h> - - -#include <assert.h> - -static constexpr Domain modplug_domain("modplug"); - -static constexpr size_t MODPLUG_FRAME_SIZE = 4096; -static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024; -static constexpr InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; - -static int modplug_loop_count; - -static bool -modplug_decoder_init(const config_param ¶m) -{ - modplug_loop_count = param.GetBlockValue("loop_count", 0); - if (modplug_loop_count < -1) - FormatFatalError("Invalid loop count in line %d: %i", - param.line, modplug_loop_count); - - return true; -} - -static WritableBuffer<uint8_t> -mod_loadfile(Decoder *decoder, InputStream &is) -{ - const InputStream::offset_type size = is.GetSize(); - - if (size == 0) { - LogWarning(modplug_domain, "file is empty"); - return { nullptr, 0 }; - } - - if (size > MODPLUG_FILE_LIMIT) { - LogWarning(modplug_domain, "file too large"); - return { nullptr, 0 }; - } - - //known/unknown size, preallocate array, lets read in chunks - - const bool is_stream = size < 0; - - WritableBuffer<uint8_t> buffer; - buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size; - buffer.data = new uint8_t[buffer.size]; - - uint8_t *const end = buffer.end(); - uint8_t *p = buffer.begin(); - - while (true) { - size_t ret = decoder_read(decoder, is, p, end - p); - if (ret == 0) { - if (is.LockIsEOF()) - /* end of file */ - break; - - /* I/O error - skip this song */ - delete[] buffer.data; - buffer.data = nullptr; - return buffer; - } - - p += ret; - if (p == end) { - if (!is_stream) - break; - - LogWarning(modplug_domain, "stream too large"); - delete[] buffer.data; - buffer.data = nullptr; - return buffer; - } - } - - buffer.size = p - buffer.data; - return buffer; -} - -static ModPlugFile * -LoadModPlugFile(Decoder *decoder, InputStream &is) -{ - const auto buffer = mod_loadfile(decoder, is); - if (buffer.IsNull()) { - LogWarning(modplug_domain, "could not load stream"); - return nullptr; - } - - ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size); - delete[] buffer.data; - return f; -} - -static void -mod_decode(Decoder &decoder, InputStream &is) -{ - ModPlug_Settings settings; - int ret; - char audio_buffer[MODPLUG_FRAME_SIZE]; - - ModPlug_GetSettings(&settings); - /* alter setting */ - settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ - settings.mChannels = 2; - settings.mBits = 16; - settings.mFrequency = 44100; - settings.mLoopCount = modplug_loop_count; - /* insert more setting changes here */ - ModPlug_SetSettings(&settings); - - ModPlugFile *f = LoadModPlugFile(&decoder, is); - if (f == nullptr) { - LogWarning(modplug_domain, "could not decode stream"); - return; - } - - static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, - is.IsSeekable(), - ModPlug_GetLength(f) / 1000.0); - - DecoderCommand cmd; - do { - ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, nullptr, - audio_buffer, ret, - 0); - - if (cmd == DecoderCommand::SEEK) { - float where = decoder_seek_where(decoder); - - ModPlug_Seek(f, (int)(where * 1000.0)); - - decoder_command_finished(decoder); - } - - } while (cmd != DecoderCommand::STOP); - - ModPlug_Unload(f); -} - -static bool -modplug_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - ModPlugFile *f = LoadModPlugFile(nullptr, is); - if (f == nullptr) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - ModPlug_GetLength(f) / 1000); - - const char *title = ModPlug_GetName(f); - if (title != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, title); - - ModPlug_Unload(f); - - return true; -} - -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", - nullptr -}; - -const struct DecoderPlugin modplug_decoder_plugin = { - "modplug", - modplug_decoder_init, - nullptr, - mod_decode, - nullptr, - nullptr, - modplug_scan_stream, - nullptr, - mod_suffixes, - nullptr, -}; diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/ModplugDecoderPlugin.hxx deleted file mode 100644 index 4cd9f5b25..000000000 --- a/src/decoder/ModplugDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_MODPLUG_HXX -#define MPD_DECODER_MODPLUG_HXX - -extern const struct DecoderPlugin modplug_decoder_plugin; - -#endif diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx deleted file mode 100644 index dc258623c..000000000 --- a/src/decoder/MpcdecDecoderPlugin.cxx +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "MpcdecDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <mpc/mpcdec.h> - -#include <assert.h> -#include <unistd.h> -#include <math.h> - -struct mpc_decoder_data { - InputStream &is; - Decoder *decoder; - - mpc_decoder_data(InputStream &_is, Decoder *_decoder) - :is(_is), decoder(_decoder) {} -}; - -static constexpr Domain mpcdec_domain("mpcdec"); - -static mpc_int32_t -mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return decoder_read(data->decoder, data->is, ptr, size); -} - -static mpc_bool_t -mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return data->is.LockSeek(offset, SEEK_SET, IgnoreError()); -} - -static mpc_int32_t -mpc_tell_cb(mpc_reader *reader) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return (long)data->is.GetOffset(); -} - -static mpc_bool_t -mpc_canseek_cb(mpc_reader *reader) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return data->is.IsSeekable(); -} - -static mpc_int32_t -mpc_getsize_cb(mpc_reader *reader) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return data->is.GetSize(); -} - -/* 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(Decoder &mpd_decoder, InputStream &is) -{ - MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; - - mpc_decoder_data data(is, &mpd_decoder); - - mpc_reader reader; - 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; - - mpc_demux *demux = mpc_demux_init(&reader); - if (demux == nullptr) { - if (decoder_get_command(mpd_decoder) != DecoderCommand::STOP) - LogWarning(mpcdec_domain, - "Not a valid musepack stream"); - return; - } - - mpc_streaminfo info; - mpc_demux_get_info(demux, &info); - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, info.sample_freq, - SampleFormat::S24_P32, - info.channels, error)) { - LogError(error); - mpc_demux_exit(demux); - return; - } - - ReplayGainInfo rgi; - rgi.Clear(); - rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); - rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; - rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); - rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; - - decoder_replay_gain(mpd_decoder, &rgi); - - decoder_initialized(mpd_decoder, audio_format, - is.IsSeekable(), - mpc_streaminfo_get_length(&info)); - - DecoderCommand cmd = DecoderCommand::NONE; - do { - if (cmd == DecoderCommand::SEEK) { - mpc_int64_t where = decoder_seek_where(mpd_decoder) * - audio_format.sample_rate; - bool success; - - success = mpc_demux_seek_sample(demux, where) - == MPC_STATUS_OK; - if (success) - decoder_command_finished(mpd_decoder); - else - decoder_seek_error(mpd_decoder); - } - - mpc_uint32_t vbr_update_bits = 0; - - mpc_frame_info frame; - frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; - mpc_status status = mpc_demux_decode(demux, &frame); - if (status != MPC_STATUS_OK) { - LogWarning(mpcdec_domain, - "Failed to decode sample"); - break; - } - - if (frame.bits == -1) - break; - - mpc_uint32_t ret = frame.samples; - ret *= info.channels; - - int32_t chunk[ARRAY_SIZE(sample_buffer)]; - mpc_to_mpd_buffer(chunk, sample_buffer, ret); - - long 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 != DecoderCommand::STOP); - - mpc_demux_exit(demux); -} - -static float -mpcdec_get_file_duration(InputStream &is) -{ - mpc_decoder_data data(is, nullptr); - - mpc_reader reader; - 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; - - mpc_demux *demux = mpc_demux_init(&reader); - if (demux == nullptr) - return -1; - - mpc_streaminfo info; - mpc_demux_get_info(demux, &info); - mpc_demux_exit(demux); - - return mpc_streaminfo_get_length(&info); -} - -static bool -mpcdec_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - float total_time = mpcdec_get_file_duration(is); - - if (total_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const mpcdec_suffixes[] = { "mpc", nullptr }; - -const struct DecoderPlugin mpcdec_decoder_plugin = { - "mpcdec", - nullptr, - nullptr, - mpcdec_decode, - nullptr, - nullptr, - mpcdec_scan_stream, - nullptr, - mpcdec_suffixes, - nullptr, -}; diff --git a/src/decoder/MpcdecDecoderPlugin.hxx b/src/decoder/MpcdecDecoderPlugin.hxx deleted file mode 100644 index 23ecc801e..000000000 --- a/src/decoder/MpcdecDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_MPCDEC_HXX -#define MPD_DECODER_MPCDEC_HXX - -extern const struct DecoderPlugin mpcdec_decoder_plugin; - -#endif diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx deleted file mode 100644 index df23f7847..000000000 --- a/src/decoder/Mpg123DecoderPlugin.cxx +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "Mpg123DecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <mpg123.h> -#include <stdio.h> - -static constexpr Domain mpg123_domain("mpg123"); - -static bool -mpd_mpg123_init(gcc_unused const config_param ¶m) -{ - mpg123_init(); - - return true; -} - -static void -mpd_mpg123_finish(void) -{ - mpg123_exit(); -} - -/** - * Opens a file with an existing #mpg123_handle. - * - * @param handle a handle which was created before; on error, this - * function will not free it - * @param audio_format this parameter is filled after successful - * return - * @return true on success - */ -static bool -mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, - AudioFormat &audio_format) -{ - int error; - int channels, encoding; - long rate; - - /* mpg123_open() wants a writable string :-( */ - char *const path2 = const_cast<char *>(path_fs); - - error = mpg123_open(handle, path2); - if (error != MPG123_OK) { - FormatWarning(mpg123_domain, - "libmpg123 failed to open %s: %s", - path_fs, mpg123_plain_strerror(error)); - return false; - } - - /* obtain the audio format */ - - error = mpg123_getformat(handle, &rate, &channels, &encoding); - if (error != MPG123_OK) { - FormatWarning(mpg123_domain, - "mpg123_getformat() failed: %s", - mpg123_plain_strerror(error)); - return false; - } - - if (encoding != MPG123_ENC_SIGNED_16) { - /* other formats not yet implemented */ - FormatWarning(mpg123_domain, - "expected MPG123_ENC_SIGNED_16, got %d", - encoding); - return false; - } - - Error error2; - if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16, - channels, error2)) { - LogError(error2); - return false; - } - - return true; -} - -static void -mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs) -{ - mpg123_handle *handle; - int error; - off_t num_samples; - struct mpg123_frameinfo info; - - /* open the file */ - - handle = mpg123_new(nullptr, &error); - if (handle == nullptr) { - FormatError(mpg123_domain, - "mpg123_new() failed: %s", - mpg123_plain_strerror(error)); - return; - } - - AudioFormat audio_format; - if (!mpd_mpg123_open(handle, path_fs, audio_format)) { - mpg123_delete(handle); - return; - } - - num_samples = mpg123_length(handle); - - /* tell MPD core we're ready */ - - decoder_initialized(decoder, audio_format, true, - (float)num_samples / - (float)audio_format.sample_rate); - - if (mpg123_info(handle, &info) != MPG123_OK) { - info.vbr = MPG123_CBR; - info.bitrate = 0; - } - - switch (info.vbr) { - case MPG123_ABR: - info.bitrate = info.abr_rate; - break; - case MPG123_CBR: - break; - default: - info.bitrate = 0; - } - - /* the decoder main loop */ - - DecoderCommand cmd; - do { - unsigned char buffer[8192]; - size_t nbytes; - - /* decode */ - - error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); - if (error != MPG123_OK) { - if (error != MPG123_DONE) - FormatWarning(mpg123_domain, - "mpg123_read() failed: %s", - mpg123_plain_strerror(error)); - break; - } - - /* update bitrate for ABR/VBR */ - if (info.vbr != MPG123_CBR) { - /* FIXME: maybe skip, as too expensive? */ - /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ - if (mpg123_info (handle, &info) != MPG123_OK) - info.bitrate = 0; - } - - /* send to MPD */ - - cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate); - - if (cmd == DecoderCommand::SEEK) { - off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; - c = mpg123_seek(handle, c, SEEK_SET); - if (c < 0) - decoder_seek_error(decoder); - else { - decoder_command_finished(decoder); - decoder_timestamp(decoder, c/(double)audio_format.sample_rate); - } - - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); - - /* cleanup */ - - mpg123_delete(handle); -} - -static bool -mpd_mpg123_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - mpg123_handle *handle; - int error; - off_t num_samples; - - handle = mpg123_new(nullptr, &error); - if (handle == nullptr) { - FormatError(mpg123_domain, - "mpg123_new() failed: %s", - mpg123_plain_strerror(error)); - return false; - } - - AudioFormat audio_format; - if (!mpd_mpg123_open(handle, path_fs, audio_format)) { - mpg123_delete(handle); - return false; - } - - num_samples = mpg123_length(handle); - if (num_samples <= 0) { - mpg123_delete(handle); - return false; - } - - /* ID3 tag support not yet implemented */ - - mpg123_delete(handle); - - tag_handler_invoke_duration(handler, handler_ctx, - num_samples / audio_format.sample_rate); - return true; -} - -static const char *const mpg123_suffixes[] = { - "mp3", - nullptr -}; - -const struct DecoderPlugin mpg123_decoder_plugin = { - "mpg123", - mpd_mpg123_init, - mpd_mpg123_finish, - /* streaming not yet implemented */ - nullptr, - mpd_mpg123_file_decode, - mpd_mpg123_scan_file, - nullptr, - nullptr, - mpg123_suffixes, - nullptr, -}; diff --git a/src/decoder/Mpg123DecoderPlugin.hxx b/src/decoder/Mpg123DecoderPlugin.hxx deleted file mode 100644 index 10f7c37f5..000000000 --- a/src/decoder/Mpg123DecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_MPG123_HXX -#define MPD_DECODER_MPG123_HXX - -extern const struct DecoderPlugin mpg123_decoder_plugin; - -#endif diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx deleted file mode 100644 index 565dbafcf..000000000 --- a/src/decoder/OggCodec.cxx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#include "config.h" -#include "OggCodec.hxx" - -#include <string.h> - -enum ogg_codec -ogg_codec_detect(Decoder *decoder, InputStream &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 = decoder_read(decoder, is, buf, sizeof(buf)); - if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - 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 OGG_CODEC_FLAC; - - if (memcmp(buf + 28, "Opus", 4) == 0) - return OGG_CODEC_OPUS; - - return OGG_CODEC_VORBIS; -} diff --git a/src/decoder/OggCodec.hxx b/src/decoder/OggCodec.hxx deleted file mode 100644 index 857871607..000000000 --- a/src/decoder/OggCodec.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#ifndef MPD_OGG_CODEC_HXX -#define MPD_OGG_CODEC_HXX - -#include "DecoderAPI.hxx" - -enum ogg_codec { - OGG_CODEC_UNKNOWN, - OGG_CODEC_VORBIS, - OGG_CODEC_FLAC, - OGG_CODEC_OPUS, -}; - -enum ogg_codec -ogg_codec_detect(Decoder *decoder, InputStream &is); - -#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx deleted file mode 100644 index 65c7fa3ce..000000000 --- a/src/decoder/OggFind.cxx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "OggFind.hxx" -#include "OggSyncState.hxx" -#include "util/Error.hxx" - -#include <stdio.h> - -bool -OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) -{ - while (true) { - int r = ogg_stream_packetout(&os, &packet); - if (r == 0) { - if (!oy.ExpectPageIn(os)) - return false; - - continue; - } else if (r > 0 && packet.e_o_s) - return true; - } -} - -bool -OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence) -{ - oy.Reset(); - - /* reset the stream to clear any previous partial packet - data */ - ogg_stream_reset(&os); - - return is.LockSeek(offset, whence, IgnoreError()) && - oy.ExpectPageSeekIn(os); -} - -bool -OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, - InputStream &is) -{ - if (is.size > 0 && is.size - is.offset < 65536) - return OggFindEOS(oy, os, packet); - - if (!is.CheapSeeking()) - return false; - - return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) && - OggFindEOS(oy, os, packet); -} diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx deleted file mode 100644 index ad51ccdf3..000000000 --- a/src/decoder/OggFind.hxx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_FIND_HXX -#define MPD_OGG_FIND_HXX - -#include "check.h" -#include "InputStream.hxx" - -#include <ogg/ogg.h> - -struct InputStream; -class OggSyncState; - -/** - * Skip all pages/packets until an end-of-stream (EOS) packet for the - * specified stream is found. - * - * @return true if the EOS packet was found - */ -bool -OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); - -/** - * Seek the #InputStream and find the next Ogg page. - */ -bool -OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence); - -/** - * Try to find the end-of-stream (EOS) packet. Seek to the end of the - * file if necessary. - * - * @return true if the EOS packet was found - */ -bool -OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, - InputStream &is); - -#endif diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx deleted file mode 100644 index 5235c1bd8..000000000 --- a/src/decoder/OggSyncState.hxx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_SYNC_STATE_HXX -#define MPD_OGG_SYNC_STATE_HXX - -#include "check.h" -#include "OggUtil.hxx" - -#include <ogg/ogg.h> - -#include <stddef.h> - -/** - * Wrapper for an ogg_sync_state. - */ -class OggSyncState { - ogg_sync_state oy; - - InputStream &is; - Decoder *const decoder; - -public: - OggSyncState(InputStream &_is, Decoder *const _decoder=nullptr) - :is(_is), decoder(_decoder) { - ogg_sync_init(&oy); - } - - ~OggSyncState() { - ogg_sync_clear(&oy); - } - - void Reset() { - ogg_sync_reset(&oy); - } - - bool Feed(size_t size) { - return OggFeed(oy, decoder, is, size); - } - - bool ExpectPage(ogg_page &page) { - return OggExpectPage(oy, page, decoder, is); - } - - bool ExpectFirstPage(ogg_stream_state &os) { - return OggExpectFirstPage(oy, os, decoder, is); - } - - bool ExpectPageIn(ogg_stream_state &os) { - return OggExpectPageIn(oy, os, decoder, is); - } - - bool ExpectPageSeek(ogg_page &page) { - return OggExpectPageSeek(oy, page, decoder, is); - } - - bool ExpectPageSeekIn(ogg_stream_state &os) { - return OggExpectPageSeekIn(oy, os, decoder, is); - } -}; - -#endif diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx deleted file mode 100644 index 8f181ce57..000000000 --- a/src/decoder/OggUtil.cxx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2003-2013 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" -#include "DecoderAPI.hxx" - -bool -OggFeed(ogg_sync_state &oy, Decoder *decoder, - InputStream &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, - Decoder *decoder, InputStream &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; - } -} - -bool -OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is) -{ - ogg_page page; - if (!OggExpectPage(oy, page, decoder, is)) - return false; - - ogg_stream_init(&os, ogg_page_serialno(&page)); - ogg_stream_pagein(&os, &page); - return true; -} - -bool -OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is) -{ - ogg_page page; - if (!OggExpectPage(oy, page, decoder, is)) - return false; - - ogg_stream_pagein(&os, &page); - return true; -} - -bool -OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, - Decoder *decoder, InputStream &input_stream) -{ - size_t remaining_skipped = 32768; - - while (true) { - int r = ogg_sync_pageseek(&oy, &page); - if (r > 0) - return true; - - if (r < 0) { - /* skipped -r bytes */ - size_t nbytes = -r; - if (nbytes > remaining_skipped) - /* still no ogg page - we lost our - patience, abort */ - return false; - - remaining_skipped -= nbytes; - continue; - } - - if (!OggFeed(oy, decoder, input_stream, 1024)) - return false; - } -} - -bool -OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is) -{ - ogg_page page; - if (!OggExpectPageSeek(oy, page, decoder, is)) - return false; - - ogg_stream_pagein(&os, &page); - return true; -} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx deleted file mode 100644 index 41fc755ba..000000000 --- a/src/decoder/OggUtil.hxx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2003-2013 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> - -struct InputStream; -struct Decoder; - -/** - * Feed data from the #InputStream into the #ogg_sync_state. - * - * @return false on error or end-of-file - */ -bool -OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &is, - 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, - Decoder *decoder, InputStream &is); - -/** - * Combines OggExpectPage(), ogg_stream_init() and - * ogg_stream_pagein(). - * - * @return true if the stream was initialized and the first page was - * delivered to it - */ -bool -OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is); - -/** - * Combines OggExpectPage() and ogg_stream_pagein(). - * - * @return true if a page was delivered to the stream - */ -bool -OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is); - -/** - * Like OggExpectPage(), but allow skipping garbage (after seeking). - */ -bool -OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, - Decoder *decoder, InputStream &is); - -/** - * Combines OggExpectPageSeek() and ogg_stream_pagein(). - * - * @return true if a page was delivered to the stream - */ -bool -OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is); - -#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx deleted file mode 100644 index 01ea3687a..000000000 --- a/src/decoder/OpusDecoderPlugin.cxx +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "OpusDomain.hxx" -#include "OpusHead.hxx" -#include "OpusTags.hxx" -#include "OggUtil.hxx" -#include "OggFind.hxx" -#include "OggSyncState.hxx" -#include "DecoderAPI.hxx" -#include "OggCodec.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagBuilder.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <opus.h> -#include <ogg/ogg.h> - -#include <glib.h> - -#include <string.h> -#include <stdio.h> - -static constexpr 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(gcc_unused const config_param ¶m) -{ - LogDebug(opus_domain, opus_get_version_string()); - - return true; -} - -class MPDOpusDecoder { - Decoder &decoder; - InputStream &input_stream; - - ogg_stream_state os; - - OpusDecoder *opus_decoder; - opus_int16 *output_buffer; - unsigned output_size; - - bool os_initialized; - bool found_opus; - - int opus_serialno; - - ogg_int64_t eos_granulepos; - - size_t frame_size; - -public: - MPDOpusDecoder(Decoder &_decoder, - InputStream &_input_stream) - :decoder(_decoder), input_stream(_input_stream), - opus_decoder(nullptr), - output_buffer(nullptr), output_size(0), - os_initialized(false), found_opus(false) {} - ~MPDOpusDecoder(); - - bool ReadFirstPage(OggSyncState &oy); - bool ReadNextPage(OggSyncState &oy); - - DecoderCommand HandlePackets(); - DecoderCommand HandlePacket(const ogg_packet &packet); - DecoderCommand HandleBOS(const ogg_packet &packet); - DecoderCommand HandleTags(const ogg_packet &packet); - DecoderCommand HandleAudio(const ogg_packet &packet); - - bool Seek(OggSyncState &oy, double where); -}; - -MPDOpusDecoder::~MPDOpusDecoder() -{ - g_free(output_buffer); - - if (opus_decoder != nullptr) - opus_decoder_destroy(opus_decoder); - - if (os_initialized) - ogg_stream_clear(&os); -} - -inline bool -MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) -{ - assert(!os_initialized); - - if (!oy.ExpectFirstPage(os)) - return false; - - os_initialized = true; - return true; -} - -inline bool -MPDOpusDecoder::ReadNextPage(OggSyncState &oy) -{ - assert(os_initialized); - - ogg_page page; - if (!oy.ExpectPage(page)) - return false; - - const auto page_serialno = ogg_page_serialno(&page); - if (page_serialno != os.serialno) - ogg_stream_reset_serialno(&os, page_serialno); - - ogg_stream_pagein(&os, &page); - return true; -} - -inline DecoderCommand -MPDOpusDecoder::HandlePackets() -{ - ogg_packet packet; - while (ogg_stream_packetout(&os, &packet) == 1) { - auto cmd = HandlePacket(packet); - if (cmd != DecoderCommand::NONE) - return cmd; - } - - return DecoderCommand::NONE; -} - -inline DecoderCommand -MPDOpusDecoder::HandlePacket(const ogg_packet &packet) -{ - if (packet.e_o_s) - return DecoderCommand::STOP; - - if (packet.b_o_s) - return HandleBOS(packet); - else if (!found_opus) - return DecoderCommand::STOP; - - if (IsOpusTags(packet)) - return HandleTags(packet); - - return HandleAudio(packet); -} - -/** - * Load the end-of-stream packet and restore the previous file - * position. - */ -static bool -LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, - ogg_packet &packet) -{ - if (!is.CheapSeeking()) - /* we do this for local files only, because seeking - around remote files is expensive and not worth the - troubl */ - return -1; - - const auto old_offset = is.offset; - if (old_offset < 0) - return -1; - - /* create temporary Ogg objects for seeking and parsing the - EOS packet */ - OggSyncState oy(is, decoder); - ogg_stream_state os; - ogg_stream_init(&os, serialno); - - bool result = OggSeekFindEOS(oy, os, packet, is); - ogg_stream_clear(&os); - - /* restore the previous file position */ - is.Seek(old_offset, SEEK_SET, IgnoreError()); - - return result; -} - -/** - * Load the end-of-stream granulepos and restore the previous file - * position. - * - * @return -1 on error - */ -gcc_pure -static ogg_int64_t -LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno) -{ - ogg_packet packet; - if (!LoadEOSPacket(is, decoder, serialno, packet)) - return -1; - - return packet.granulepos; -} - -inline DecoderCommand -MPDOpusDecoder::HandleBOS(const ogg_packet &packet) -{ - assert(packet.b_o_s); - - if (found_opus || !IsOpusHead(packet)) - return DecoderCommand::STOP; - - unsigned channels; - if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || - !audio_valid_channel_count(channels)) - return DecoderCommand::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) { - FormatError(opus_domain, "libopus error: %s", - opus_strerror(opus_error)); - return DecoderCommand::STOP; - } - - eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, - opus_serialno); - const double duration = eos_granulepos >= 0 - ? double(eos_granulepos) / opus_sample_rate - : -1.0; - - const AudioFormat audio_format(opus_sample_rate, - SampleFormat::S16, channels); - decoder_initialized(decoder, audio_format, - eos_granulepos > 0, duration); - frame_size = audio_format.GetFrameSize(); - - /* 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); -} - -inline DecoderCommand -MPDOpusDecoder::HandleTags(const ogg_packet &packet) -{ - ReplayGainInfo rgi; - rgi.Clear(); - - TagBuilder tag_builder; - - DecoderCommand cmd; - if (ScanOpusTags(packet.packet, packet.bytes, - &rgi, - &add_tag_handler, &tag_builder) && - !tag_builder.IsEmpty()) { - decoder_replay_gain(decoder, &rgi); - - Tag tag; - tag_builder.Commit(tag); - cmd = decoder_tag(decoder, input_stream, std::move(tag)); - } else - cmd = decoder_get_command(decoder); - - return cmd; -} - -inline DecoderCommand -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) { - LogError(opus_domain, opus_strerror(nframes)); - return DecoderCommand::STOP; - } - - if (nframes > 0) { - const size_t nbytes = nframes * frame_size; - auto cmd = decoder_data(decoder, input_stream, - output_buffer, nbytes, - 0); - if (cmd != DecoderCommand::NONE) - return cmd; - - if (packet.granulepos > 0) - decoder_timestamp(decoder, - double(packet.granulepos) - / opus_sample_rate); - } - - return DecoderCommand::NONE; -} - -bool -MPDOpusDecoder::Seek(OggSyncState &oy, double where_s) -{ - assert(eos_granulepos > 0); - assert(input_stream.seekable); - assert(input_stream.size > 0); - assert(input_stream.offset >= 0); - - const ogg_int64_t where_granulepos(where_s * opus_sample_rate); - - /* interpolate the file offset where we expect to find the - given granule position */ - /* TODO: implement binary search */ - InputStream::offset_type offset(where_granulepos * input_stream.size - / eos_granulepos); - - if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET)) - return false; - - decoder_timestamp(decoder, where_s); - return true; -} - -static void -mpd_opus_stream_decode(Decoder &decoder, - InputStream &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.LockRewind(IgnoreError()); - - MPDOpusDecoder d(decoder, input_stream); - OggSyncState oy(input_stream, &decoder); - - if (!d.ReadFirstPage(oy)) - return; - - while (true) { - auto cmd = d.HandlePackets(); - if (cmd == DecoderCommand::SEEK) { - if (d.Seek(oy, decoder_seek_where(decoder))) - decoder_command_finished(decoder); - else - decoder_seek_error(decoder); - - continue; - } - - if (cmd != DecoderCommand::NONE) - break; - - if (!d.ReadNextPage(oy)) - break; - } -} - -static bool -mpd_opus_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - OggSyncState oy(is); - - ogg_stream_state os; - if (!oy.ExpectFirstPage(os)) - return false; - - /* read at most two more pages */ - unsigned remaining_pages = 2; - - bool result = false; - - 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 (!oy.ExpectPageIn(os)) { - result = false; - break; - } - - 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, - nullptr, - handler, handler_ctx)) - result = false; - - break; - } - } - - if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) - tag_handler_invoke_duration(handler, handler_ctx, - packet.granulepos / opus_sample_rate); - - ogg_stream_clear(&os); - - return result; -} - -static const char *const opus_suffixes[] = { - "opus", - "ogg", - "oga", - nullptr -}; - -static const char *const opus_mime_types[] = { - "audio/opus", - nullptr -}; - -const struct DecoderPlugin 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 deleted file mode 100644 index 263ac6e2d..000000000 --- a/src/decoder/OpusDecoderPlugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 DecoderPlugin opus_decoder_plugin; - -#endif diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/OpusDomain.cxx deleted file mode 100644 index b00e2a553..000000000 --- a/src/decoder/OpusDomain.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "OpusDomain.hxx" -#include "util/Domain.hxx" - -const Domain opus_domain("opus"); diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/OpusDomain.hxx deleted file mode 100644 index 2b56c427c..000000000 --- a/src/decoder/OpusDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_DOMAIN_HXX -#define MPD_OPUS_DOMAIN_HXX - -#include "check.h" - -extern const class Domain opus_domain; - -#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx deleted file mode 100644 index 0417d3905..000000000 --- a/src/decoder/OpusHead.cxx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 deleted file mode 100644 index fa6a2b666..000000000 --- a/src/decoder/OpusHead.hxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 deleted file mode 100644 index 2bb39b748..000000000 --- a/src/decoder/OpusReader.hxx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 <algorithm> - -#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; - - char *dest = new char[length + 1]; - *std::copy_n(src, length, dest) = 0; - return dest; - } -}; - -#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx deleted file mode 100644 index f7729e5ad..000000000 --- a/src/decoder/OpusTags.cxx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2013 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.hxx" -#include "tag/TagHandler.hxx" -#include "tag/Tag.hxx" -#include "ReplayGainInfo.hxx" - -#include <stdint.h> -#include <string.h> -#include <stdlib.h> - -gcc_pure -static TagType -ParseOpusTagName(const char *name) -{ - TagType type = tag_name_parse_i(name); - if (type != TAG_NUM_OF_ITEM_TYPES) - return type; - - return tag_table_lookup_i(xiph_tags, name); -} - -static void -ScanOneOpusTag(const char *name, const char *value, - ReplayGainInfo *rgi, - const struct tag_handler *handler, void *ctx) -{ - if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) { - /* R128_TRACK_GAIN is a Q7.8 fixed point number in - dB */ - - char *endptr; - long l = strtol(value, &endptr, 10); - if (endptr > value && *endptr == 0) - rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.; - } - - tag_handler_invoke_pair(handler, ctx, name, value); - - if (handler->tag != nullptr) { - TagType t = ParseOpusTagName(name); - if (t != TAG_NUM_OF_ITEM_TYPES) - tag_handler_invoke_tag(handler, ctx, t, value); - } -} - -bool -ScanOpusTags(const void *data, size_t size, - ReplayGainInfo *rgi, - 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, rgi, handler, ctx); - } - - delete[] p; - } - - return true; -} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx deleted file mode 100644 index e1f1a1ff1..000000000 --- a/src/decoder/OpusTags.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 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> - -struct ReplayGainInfo; - -bool -ScanOpusTags(const void *data, size_t size, - ReplayGainInfo *rgi, - const struct tag_handler *handler, void *ctx); - -#endif diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx deleted file mode 100644 index dbc38fb76..000000000 --- a/src/decoder/PcmDecoderPlugin.cxx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2013 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/PcmDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "util/ByteReverse.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <unistd.h> -#include <string.h> -#include <stdio.h> /* for SEEK_SET */ - -static void -pcm_stream_decode(Decoder &decoder, InputStream &is) -{ - static constexpr AudioFormat audio_format = { - 44100, - SampleFormat::S16, - 2, - }; - - const char *const mime = is.GetMimeType(); - const bool reverse_endian = mime != nullptr && - strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; - - const double time_to_size = audio_format.GetTimeToSize(); - - float total_time = -1; - const auto size = is.GetSize(); - if (size >= 0) - total_time = size / time_to_size; - - decoder_initialized(decoder, audio_format, - is.IsSeekable(), total_time); - - DecoderCommand cmd; - do { - char buffer[4096]; - - size_t nbytes = decoder_read(decoder, is, - buffer, sizeof(buffer)); - - if (nbytes == 0 && is.LockIsEOF()) - break; - - if (reverse_endian) - /* make sure we deliver samples in host byte order */ - reverse_bytes_16((uint16_t *)buffer, - (uint16_t *)buffer, - (uint16_t *)(buffer + nbytes)); - - cmd = nbytes > 0 - ? decoder_data(decoder, is, - buffer, nbytes, 0) - : decoder_get_command(decoder); - if (cmd == DecoderCommand::SEEK) { - InputStream::offset_type offset(time_to_size * - decoder_seek_where(decoder)); - - Error error; - if (is.LockSeek(offset, SEEK_SET, error)) { - decoder_command_finished(decoder); - } else { - LogError(error); - decoder_seek_error(decoder); - } - - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); -} - -static const char *const pcm_mime_types[] = { - /* for streams obtained by the cdio_paranoia input plugin */ - "audio/x-mpd-cdda-pcm", - - /* same as above, but with reverse byte order */ - "audio/x-mpd-cdda-pcm-reverse", - - nullptr -}; - -const struct DecoderPlugin pcm_decoder_plugin = { - "pcm", - nullptr, - nullptr, - pcm_stream_decode, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - pcm_mime_types, -}; diff --git a/src/decoder/PcmDecoderPlugin.hxx b/src/decoder/PcmDecoderPlugin.hxx deleted file mode 100644 index 38e4a5020..000000000 --- a/src/decoder/PcmDecoderPlugin.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 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. - */ - -/** \file - * - * Not really a decoder; this plugin forwards its input data "as-is". - * - * It was written only to support the "cdio_paranoia" input plugin, - * which does not need a decoder. - */ - -#ifndef MPD_DECODER_PCM_HXX -#define MPD_DECODER_PCM_HXX - -extern const struct DecoderPlugin pcm_decoder_plugin; - -#endif diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/SidplayDecoderPlugin.cxx deleted file mode 100644 index 160337594..000000000 --- a/src/decoder/SidplayDecoderPlugin.cxx +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "SidplayDecoderPlugin.hxx" -#include "../DecoderAPI.hxx" -#include "tag/TagHandler.hxx" -#include "util/Domain.hxx" -#include "system/ByteOrder.hxx" -#include "Log.hxx" - -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <glib.h> - -#include <sidplay/sidplay2.h> -#include <sidplay/builders/resid.h> -#include <sidplay/utils/SidTuneMod.h> - -#define SUBTUNE_PREFIX "tune_" - -static constexpr Domain sidplay_domain("sidplay"); - -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 = nullptr; - gchar *data; - gsize size; - - if (!g_file_get_contents(path, &data, &size, &error)) { - FormatError(sidplay_domain, - "unable to read songlengths file %s: %s", - path, error->message); - g_error_free(error); - return nullptr; - } - - /* 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) { - FormatError(sidplay_domain, - "unable to parse songlengths file %s: %s", - path, error->message); - g_error_free(error); - g_key_file_free(db); - return nullptr; - } - - g_key_file_set_list_separator(db, ' '); - return db; -} - -static bool -sidplay_init(const config_param ¶m) -{ - /* read the songlengths database file */ - songlength_file = param.GetBlockValue("songlength_database"); - if (songlength_file != nullptr) - songlength_database = sidplay_load_songlength_db(songlength_file); - - default_songlength = param.GetBlockValue("default_songlength", 0u); - - all_files_are_containers = - param.GetBlockValue("all_files_are_containers", true); - - path_with_subtune=g_pattern_spec_new( - "*/" SUBTUNE_PREFIX "???.sid"); - - filter_setting = param.GetBlockValue("filter", true); - - return true; -} - -static 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, nullptr)) - 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 unsigned -get_song_num(const char *path_fs) -{ - if(g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, nullptr)) { - char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); - if(!sub) return 1; - - sub+=strlen("/" SUBTUNE_PREFIX); - int song_num=strtol(sub, nullptr, 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 == nullptr) - return -1; - - gchar *sid_file=get_container_name(path_fs); - SidTuneMod tune(sid_file); - g_free(sid_file); - if(!tune) { - LogWarning(sidplay_domain, - "failed to load file for calculating md5 sum"); - return -1; - } - char md5sum[SIDTUNE_MD5_LENGTH+1]; - tune.createMD5(md5sum); - - const unsigned song_num = get_song_num(path_fs); - - gsize num_items; - gchar **values=g_key_file_get_string_list(songlength_database, - "Database", md5sum, &num_items, nullptr); - if(!values || song_num>num_items) { - g_strfreev(values); - return -1; - } - - int minutes=strtol(values[song_num-1], nullptr, 10); - if(errno==EINVAL) minutes=0; - - int seconds; - char *ptr=strchr(values[song_num-1], ':'); - if(ptr) { - seconds=strtol(ptr+1, nullptr, 10); - if(errno==EINVAL) seconds=0; - } else - seconds=0; - - g_strfreev(values); - - return (minutes*60)+seconds; -} - -static void -sidplay_file_decode(Decoder &decoder, const char *path_fs) -{ - int channels; - - /* load the tune */ - - char *path_container=get_container_name(path_fs); - SidTune tune(path_container, nullptr, true); - g_free(path_container); - if (!tune) { - LogWarning(sidplay_domain, "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) { - FormatWarning(sidplay_domain, - "sidplay2.load() failed: %s", player.error()); - return; - } - - /* initialize the builder */ - - ReSIDBuilder builder("ReSID"); - if (!builder) { - LogWarning(sidplay_domain, - "failed to initialize ReSIDBuilder"); - return; - } - - builder.create(player.info().maxsids); - if (!builder) { - LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); - return; - } - - builder.filter(filter_setting); - if (!builder) { - LogWarning(sidplay_domain, "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.precision = 16; - config.sidDefault = SID2_MOS6581; - config.sidEmulation = &builder; - config.sidModel = SID2_MODEL_CORRECT; - config.sidSamples = true; - config.sampleFormat = IsLittleEndian() - ? SID2_LITTLE_SIGNED - : SID2_BIG_SIGNED; - if (tune.isStereo()) { - config.playback = sid2_stereo; - channels = 2; - } else { - config.playback = sid2_mono; - channels = 1; - } - - iret = player.config(config); - if (iret != 0) { - FormatWarning(sidplay_domain, - "sidplay2.config() failed: %s", player.error()); - return; - } - - /* initialize the MPD decoder */ - - const AudioFormat audio_format(48000, SampleFormat::S16, channels); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, true, (float)song_len); - - /* .. and play */ - - const unsigned timebase = player.timebase(); - song_len *= timebase; - - DecoderCommand 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, nullptr, buffer, nbytes, 0); - - if (cmd == DecoderCommand::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<data_time) { - player.stop(); - data_time=0; - } - - /* ignore data until target time is reached */ - while(data_time<target_time) { - nbytes=player.play(buffer, sizeof(buffer)); - if(nbytes==0) - break; - data_time = player.time(); - } - - decoder_command_finished(decoder); - } - - if (song_len > 0 && player.time() >= (unsigned)song_len) - break; - - } while (cmd != DecoderCommand::STOP); -} - -static bool -sidplay_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - int song_num=get_song_num(path_fs); - char *path_container=get_container_name(path_fs); - - SidTune tune(path_container, nullptr, true); - g_free(path_container); - if (!tune) - return false; - - const SidTuneInfo &info = tune.getInfo(); - - /* title */ - const char *title; - if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr) - title=info.infoString[0]; - else - title=""; - - if(info.songs>1) { - char tag_title[1024]; - snprintf(tag_title, sizeof(tag_title), - "%s (%d/%d)", - title, song_num, info.songs); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, tag_title); - } else - tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); - - /* artist */ - if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, - info.infoString[1]); - - /* track */ - char track[16]; - sprintf(track, "%d", song_num); - tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); - - /* time */ - int song_len=get_song_length(path_fs); - if (song_len >= 0) - tag_handler_invoke_duration(handler, handler_ctx, song_len); - - return true; -} - -static char * -sidplay_container_scan(const char *path_fs, const unsigned int tnum) -{ - SidTune tune(path_fs, nullptr, true); - if (!tune) - return nullptr; - - const SidTuneInfo &info=tune.getInfo(); - - /* Don't treat sids containing a single tune - as containers */ - if(!all_files_are_containers && info.songs<2) - return nullptr; - - /* 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 nullptr; -} - -static const char *const sidplay_suffixes[] = { - "sid", - "mus", - "str", - "prg", - "P00", - nullptr -}; - -extern const struct DecoderPlugin sidplay_decoder_plugin; -const struct DecoderPlugin sidplay_decoder_plugin = { - "sidplay", - sidplay_init, - sidplay_finish, - nullptr, /* stream_decode() */ - sidplay_file_decode, - sidplay_scan_file, - nullptr, /* stream_tag() */ - sidplay_container_scan, - sidplay_suffixes, - nullptr, /* mime_types */ -}; diff --git a/src/decoder/SidplayDecoderPlugin.hxx b/src/decoder/SidplayDecoderPlugin.hxx deleted file mode 100644 index 16544801f..000000000 --- a/src/decoder/SidplayDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_SIDPLAY_HXX -#define MPD_DECODER_SIDPLAY_HXX - -extern const struct DecoderPlugin sidplay_decoder_plugin; - -#endif diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx deleted file mode 100644 index bcdf6d7ca..000000000 --- a/src/decoder/SndfileDecoderPlugin.cxx +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "SndfileDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <sndfile.h> - -static constexpr Domain sndfile_domain("sndfile"); - -struct SndfileInputStream { - Decoder *const decoder; - InputStream &is; - - size_t Read(void *buffer, size_t size) { - /* libsndfile chokes on partial reads; therefore - always force full reads */ - return decoder_read_full(decoder, is, buffer, size) - ? size - : 0; - } -}; - -static sf_count_t -sndfile_vio_get_filelen(void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - const InputStream &is = sis.is; - - return is.GetSize(); -} - -static sf_count_t -sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - InputStream &is = sis.is; - - Error error; - if (!is.LockSeek(offset, whence, error)) { - LogError(error, "Seek failed"); - return -1; - } - - return is.GetOffset(); -} - -static sf_count_t -sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - - return sis.Read(ptr, count); -} - -static sf_count_t -sndfile_vio_write(gcc_unused const void *ptr, - gcc_unused sf_count_t count, - gcc_unused void *user_data) -{ - /* no writing! */ - return -1; -} - -static sf_count_t -sndfile_vio_tell(void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - const InputStream &is = sis.is; - - return is.GetOffset(); -} - -/** - * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a - * libsndfile stream. - */ -static SF_VIRTUAL_IO vio = { - sndfile_vio_get_filelen, - sndfile_vio_seek, - sndfile_vio_read, - sndfile_vio_write, - sndfile_vio_tell, -}; - -/** - * Converts a frame number to a timestamp (in seconds). - */ -static float -frame_to_time(sf_count_t frame, const AudioFormat *audio_format) -{ - return (float)frame / (float)audio_format->sample_rate; -} - -/** - * Converts a timestamp (in seconds) to a frame number. - */ -static sf_count_t -time_to_frame(float t, const AudioFormat *audio_format) -{ - return (sf_count_t)(t * audio_format->sample_rate); -} - -static void -sndfile_stream_decode(Decoder &decoder, InputStream &is) -{ - SNDFILE *sf; - SF_INFO info; - size_t frame_size; - sf_count_t read_frames, num_frames; - int buffer[4096]; - - info.format = 0; - - SndfileInputStream sis{&decoder, is}; - sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); - if (sf == nullptr) { - LogWarning(sndfile_domain, "sf_open_virtual() failed"); - return; - } - - /* for now, always read 32 bit samples. Later, we could lower - MPD's CPU usage by reading 16 bit samples with - sf_readf_short() on low-quality source files. */ - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, info.samplerate, - SampleFormat::S32, - info.channels, error)) { - LogError(error); - return; - } - - decoder_initialized(decoder, audio_format, info.seekable, - frame_to_time(info.frames, &audio_format)); - - frame_size = audio_format.GetFrameSize(); - read_frames = sizeof(buffer) / frame_size; - - DecoderCommand cmd; - do { - num_frames = sf_readf_int(sf, buffer, read_frames); - if (num_frames <= 0) - break; - - cmd = decoder_data(decoder, is, - buffer, num_frames * frame_size, - 0); - if (cmd == DecoderCommand::SEEK) { - sf_count_t c = - time_to_frame(decoder_seek_where(decoder), - &audio_format); - c = sf_seek(sf, c, SEEK_SET); - if (c < 0) - decoder_seek_error(decoder); - else - decoder_command_finished(decoder); - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); - - sf_close(sf); -} - -static bool -sndfile_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - SNDFILE *sf; - SF_INFO info; - const char *p; - - info.format = 0; - - sf = sf_open(path_fs, SFM_READ, &info); - if (sf == nullptr) - return false; - - if (!audio_valid_sample_rate(info.samplerate)) { - sf_close(sf); - FormatWarning(sndfile_domain, - "Invalid sample rate in %s", path_fs); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - info.frames / info.samplerate); - - p = sf_get_string(sf, SF_STR_TITLE); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, p); - - p = sf_get_string(sf, SF_STR_ARTIST); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, p); - - p = sf_get_string(sf, SF_STR_DATE); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, p); - - sf_close(sf); - - return true; -} - -static const char *const sndfile_suffixes[] = { - "wav", "aiff", "aif", /* Microsoft / SGI / Apple */ - "au", "snd", /* Sun / DEC / NeXT */ - "paf", /* Paris Audio File */ - "iff", "svx", /* Commodore Amiga IFF / SVX */ - "sf", /* IRCAM */ - "voc", /* Creative */ - "w64", /* Soundforge */ - "pvf", /* Portable Voice Format */ - "xi", /* Fasttracker */ - "htk", /* HMM Tool Kit */ - "caf", /* Apple */ - "sd2", /* Sound Designer II */ - - /* libsndfile also supports FLAC and Ogg Vorbis, but only by - linking with libFLAC and libvorbis - we can do better, we - have native plugins for these libraries */ - - nullptr -}; - -static const char *const sndfile_mime_types[] = { - "audio/x-wav", - "audio/x-aiff", - - /* what are the MIME types of the other supported formats? */ - - nullptr -}; - -const struct DecoderPlugin sndfile_decoder_plugin = { - "sndfile", - nullptr, - nullptr, - sndfile_stream_decode, - nullptr, - sndfile_scan_file, - nullptr, - nullptr, - sndfile_suffixes, - sndfile_mime_types, -}; diff --git a/src/decoder/SndfileDecoderPlugin.hxx b/src/decoder/SndfileDecoderPlugin.hxx deleted file mode 100644 index f8aa65680..000000000 --- a/src/decoder/SndfileDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_SNDFILE_HXX -#define MPD_DECODER_SNDFILE_HXX - -extern const struct DecoderPlugin sndfile_decoder_plugin; - -#endif diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx deleted file mode 100644 index d4f019b58..000000000 --- a/src/decoder/VorbisComments.cxx +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "VorbisComments.hxx" -#include "XiphTags.hxx" -#include "tag/Tag.hxx" -#include "tag/TagTable.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagBuilder.hxx" -#include "ReplayGainInfo.hxx" -#include "util/ASCII.hxx" - -#include <glib.h> - -#include <assert.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> - -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (StringEqualsCaseASCII(comment, needle, len) && - comment[len] == '=') - return comment + len + 1; - - return nullptr; -} - -bool -vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) -{ - rgi.Clear(); - - const char *temp; - bool found = false; - - 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++; - } - - return found; -} - -/** - * 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(const char *comment, - const char *name, TagType tag_type, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != nullptr) { - tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); - return true; - } - - return false; -} - -static void -vorbis_scan_comment(const char *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != nullptr) { - char *name = g_strdup((const char*)comment); - char *value = strchr(name, '='); - - if (value != nullptr && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) - if (vorbis_copy_comment(comment, i->name, i->type, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (vorbis_copy_comment(comment, - tag_item_names[i], TagType(i), - handler, handler_ctx)) - return; -} - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx) -{ - while (*comments) - vorbis_scan_comment(*comments++, - handler, handler_ctx); - -} - -Tag * -vorbis_comments_to_tag(char **comments) -{ - TagBuilder tag_builder; - vorbis_comments_scan(comments, &add_tag_handler, &tag_builder); - return tag_builder.IsEmpty() - ? nullptr - : tag_builder.Commit(); -} diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx deleted file mode 100644 index e5a48ef6b..000000000 --- a/src/decoder/VorbisComments.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_VORBIS_COMMENTS_HXX -#define MPD_VORBIS_COMMENTS_HXX - -#include "check.h" - -struct ReplayGainInfo; -struct tag_handler; -struct Tag; - -bool -vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments); - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx); - -Tag * -vorbis_comments_to_tag(char **comments); - -#endif diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx deleted file mode 100644 index 4d3e48528..000000000 --- a/src/decoder/VorbisDecoderPlugin.cxx +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "VorbisDecoderPlugin.h" -#include "VorbisComments.hxx" -#include "VorbisDomain.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "OggCodec.hxx" -#include "util/Error.hxx" -#include "util/UriUtil.hxx" -#include "util/Macros.hxx" -#include "system/ByteOrder.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "Log.hxx" - -#ifndef HAVE_TREMOR -#define OV_EXCLUDE_STATIC_CALLBACKS -#include <vorbis/vorbisfile.h> -#else -#include <tremor/ivorbisfile.h> -/* 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 <assert.h> -#include <errno.h> - -struct vorbis_input_stream { - Decoder *decoder; - - InputStream *input_stream; - bool seekable; -}; - -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) -{ - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; - size_t ret = decoder_read(vis->decoder, *vis->input_stream, - ptr, size * nmemb); - - errno = 0; - - return ret / size; -} - -static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) -{ - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; - - Error error; - return vis->seekable && - (vis->decoder == nullptr || - decoder_get_command(*vis->decoder) != DecoderCommand::STOP) && - vis->input_stream->LockSeek(offset, whence, error) - ? 0 : -1; -} - -/* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(gcc_unused void *data) -{ - return 0; -} - -static long ogg_tell_cb(void *data) -{ - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; - - return (long)vis->input_stream->offset; -} - -static const ov_callbacks vorbis_is_callbacks = { - ogg_read_cb, - ogg_seek_cb, - ogg_close_cb, - ogg_tell_cb, -}; - -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 bool -vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, - Decoder *decoder, InputStream &input_stream) -{ - vis->decoder = decoder; - vis->input_stream = &input_stream; - vis->seekable = input_stream.CheapSeeking(); - - int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); - if (ret < 0) { - if (decoder == nullptr || - decoder_get_command(*decoder) == DecoderCommand::NONE) - FormatWarning(vorbis_domain, - "Failed to open Ogg Vorbis stream: %s", - vorbis_strerror(ret)); - return false; - } - - return true; -} - -static void -vorbis_send_comments(Decoder &decoder, InputStream &is, - char **comments) -{ - Tag *tag = vorbis_comments_to_tag(comments); - if (!tag) - return; - - decoder_tag(decoder, is, std::move(*tag)); - delete 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(Decoder &decoder, - InputStream &input_stream) -{ - if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_VORBIS) - return; - - /* rewind the stream, because ogg_codec_detect() has - moved it */ - input_stream.LockRewind(IgnoreError()); - - struct vorbis_input_stream vis; - OggVorbis_File vf; - if (!vorbis_is_open(&vis, &vf, &decoder, input_stream)) - return; - - const vorbis_info *vi = ov_info(&vf, -1); - if (vi == nullptr) { - LogWarning(vorbis_domain, "ov_info() has failed"); - return; - } - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, vi->rate, -#ifdef HAVE_TREMOR - SampleFormat::S16, -#else - SampleFormat::FLOAT, -#endif - vi->channels, error)) { - LogError(error); - return; - } - - float total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - - decoder_initialized(decoder, audio_format, vis.seekable, total_time); - -#ifdef HAVE_TREMOR - char buffer[4096]; -#else - float buffer[2048]; - const int frames_per_buffer = - ARRAY_SIZE(buffer) / audio_format.channels; - const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; -#endif - - int prev_section = -1; - unsigned kbit_rate = 0; - - DecoderCommand cmd = decoder_get_command(decoder); - do { - if (cmd == DecoderCommand::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); - } - - int current_section; - -#ifdef HAVE_TREMOR - long nbytes = ov_read(&vf, buffer, sizeof(buffer), - IsBigEndian(), 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) { - vi = ov_info(&vf, -1); - if (vi == nullptr) { - LogWarning(vorbis_domain, - "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 */ - LogWarning(vorbis_domain, - "audio format change, stopping here"); - break; - } - - char **comments = ov_comment(&vf, -1)->user_comments; - vorbis_send_comments(decoder, input_stream, comments); - - ReplayGainInfo rgi; - if (vorbis_comments_to_replay_gain(rgi, comments)) - decoder_replay_gain(decoder, &rgi); - - prev_section = current_section; - } - - long test = ov_bitrate_instant(&vf); - if (test > 0) - kbit_rate = test / 1000; - - cmd = decoder_data(decoder, input_stream, - buffer, nbytes, - kbit_rate); - } while (cmd != DecoderCommand::STOP); - - ov_clear(&vf); -} - -static bool -vorbis_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct vorbis_input_stream vis; - OggVorbis_File vf; - - if (!vorbis_is_open(&vis, &vf, nullptr, is)) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - (int)(ov_time_total(&vf, -1) + 0.5)); - - vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, - handler, handler_ctx); - - ov_clear(&vf); - return true; -} - -static const char *const vorbis_suffixes[] = { - "ogg", "oga", nullptr -}; - -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", - nullptr -}; - -const struct DecoderPlugin vorbis_decoder_plugin = { - "vorbis", - nullptr, - nullptr, - vorbis_stream_decode, - nullptr, - nullptr, - vorbis_scan_stream, - nullptr, - vorbis_suffixes, - vorbis_mime_types -}; diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/VorbisDecoderPlugin.h deleted file mode 100644 index 54953d83a..000000000 --- a/src/decoder/VorbisDecoderPlugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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_VORBIS_H -#define MPD_DECODER_VORBIS_H - -extern const struct DecoderPlugin vorbis_decoder_plugin; - -#endif diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/VorbisDomain.cxx deleted file mode 100644 index 32ff4d6b7..000000000 --- a/src/decoder/VorbisDomain.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "VorbisDomain.hxx" -#include "util/Domain.hxx" - -const Domain vorbis_domain("vorbis"); diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/VorbisDomain.hxx deleted file mode 100644 index a35edd041..000000000 --- a/src/decoder/VorbisDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_VORBIS_DOMAIN_HXX -#define MPD_VORBIS_DOMAIN_HXX - -#include "check.h" - -extern const class Domain vorbis_domain; - -#endif diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx deleted file mode 100644 index 98555c5e8..000000000 --- a/src/decoder/WavpackDecoderPlugin.cxx +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "WavpackDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "tag/ApeTag.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <wavpack/wavpack.h> -#include <glib.h> - -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> - -#define ERRORLEN 80 - -static constexpr Domain wavpack_domain("wavpack"); - -/** 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 = (int32_t *)buffer; - - switch (bytes_per_sample) { - case 1: { - int8_t *dst = (int8_t *)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. - */ - static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); - - /* pass through and align 8-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - case 2: { - uint16_t *dst = (uint16_t *)buffer; - static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); - - /* 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(gcc_unused int bytes_per_sample, void *buffer, - uint32_t count) -{ - float *p = (float *)buffer; - - while (count--) { - *p /= (1 << 23); - ++p; - } -} - -/** - * Choose a MPD sample format from libwavpacks' number of bits. - */ -static SampleFormat -wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) -{ - if (is_float) - return SampleFormat::FLOAT; - - switch (bytes_per_sample) { - case 1: - return SampleFormat::S8; - - case 2: - return SampleFormat::S16; - - case 3: - return SampleFormat::S24_P32; - - case 4: - return SampleFormat::S32; - - default: - return SampleFormat::UNDEFINED; - } -} - -/* - * This does the main decoding thing. - * Requires an already opened WavpackContext. - */ -static void -wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek) -{ - bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; - SampleFormat sample_format = - wavpack_bits_to_sample_format(is_float, - WavpackGetBytesPerSample(wpc)); - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, - WavpackGetSampleRate(wpc), - sample_format, - WavpackGetNumChannels(wpc), error)) { - LogError(error); - return; - } - - const format_samples_t format_samples = is_float - ? format_samples_float - : format_samples_int; - - const float total_time = float(WavpackGetNumSamples(wpc)) - / audio_format.sample_rate; - - const int bytes_per_sample = WavpackGetBytesPerSample(wpc); - const int output_sample_size = audio_format.GetFrameSize(); - - /* wavpack gives us all kind of samples in a 32-bit space */ - int32_t chunk[1024]; - const uint32_t samples_requested = ARRAY_SIZE(chunk) / - audio_format.channels; - - decoder_initialized(decoder, audio_format, can_seek, total_time); - - DecoderCommand cmd = decoder_get_command(decoder); - while (cmd != DecoderCommand::STOP) { - if (cmd == DecoderCommand::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); - } - } - - uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, - samples_requested); - if (samples_got == 0) - break; - - int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + - 0.5); - format_samples(bytes_per_sample, chunk, - samples_got * audio_format.channels); - - cmd = decoder_data(decoder, nullptr, chunk, - samples_got * output_sample_size, - bitrate); - } -} - -/** - * 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]; - if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0) - return false; - - *value_r = atof(buffer); - return true; -} - -static bool -wavpack_replaygain(ReplayGainInfo &rgi, - WavpackContext *wpc) -{ - rgi.Clear(); - - bool found = false; - found |= wavpack_tag_float(wpc, "replaygain_track_gain", - &rgi.tuples[REPLAY_GAIN_TRACK].gain); - found |= wavpack_tag_float(wpc, "replaygain_track_peak", - &rgi.tuples[REPLAY_GAIN_TRACK].peak); - found |= wavpack_tag_float(wpc, "replaygain_album_gain", - &rgi.tuples[REPLAY_GAIN_ALBUM].gain); - found |= wavpack_tag_float(wpc, "replaygain_album_peak", - &rgi.tuples[REPLAY_GAIN_ALBUM].peak); - - return found; -} - -static void -wavpack_scan_tag_item(WavpackContext *wpc, const char *name, - TagType type, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[1024]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_tag(handler, handler_ctx, type, buffer); - -} - -static void -wavpack_scan_pair(WavpackContext *wpc, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[8192]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_pair(handler, handler_ctx, name, buffer); -} - -/* - * Reads metainfo from the specified file. - */ -static bool -wavpack_scan_file(const char *fname, - const struct tag_handler *handler, void *handler_ctx) -{ - char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); - if (wpc == nullptr) { - FormatError(wavpack_domain, - "failed to open WavPack file \"%s\": %s", - fname, error); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - WavpackGetNumSamples(wpc) / - WavpackGetSampleRate(wpc)); - - /* the WavPack format implies APEv2 tags, which means we can - reuse the mapping from tag_ape.c */ - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - const char *name = tag_item_names[i]; - if (name != nullptr) - wavpack_scan_tag_item(wpc, name, (TagType)i, - handler, handler_ctx); - } - - for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i) - wavpack_scan_tag_item(wpc, i->name, i->type, - handler, handler_ctx); - - if (handler->pair != nullptr) { - char name[64]; - - for (int i = 0, n = WavpackGetNumTagItems(wpc); - i < n; ++i) { - int len = WavpackGetTagItemIndexed(wpc, i, name, - sizeof(name)); - if (len <= 0 || (unsigned)len >= sizeof(name)) - continue; - - wavpack_scan_pair(wpc, name, handler, handler_ctx); - } - } - - WavpackCloseFile(wpc); - - return true; -} - -/* - * mpd input_stream <=> WavpackStreamReader wrapper callbacks - */ - -/* This struct is needed for per-stream last_byte storage. */ -struct wavpack_input { - Decoder *decoder; - InputStream *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 (struct wavpack_input *)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 wpin(id)->is->LockSeek(pos, SEEK_SET, IgnoreError()) ? 0 : -1; -} - -static int -wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) -{ - return wpin(id)->is->LockSeek(delta, mode, IgnoreError()) ? 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 = { - wavpack_input_read_bytes, - wavpack_input_get_pos, - wavpack_input_set_pos_abs, - wavpack_input_set_pos_rel, - wavpack_input_push_back_byte, - wavpack_input_get_length, - wavpack_input_can_seek, - nullptr /* no need to write edited tags */ -}; - -static void -wavpack_input_init(struct wavpack_input *isp, Decoder &decoder, - InputStream &is) -{ - isp->decoder = &decoder; - isp->is = &is; - isp->last_byte = EOF; -} - -static InputStream * -wavpack_open_wvc(Decoder &decoder, const char *uri, - Mutex &mutex, Cond &cond, - struct wavpack_input *wpi) -{ - /* - * As we use dc->utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - if (uri == nullptr) - return nullptr; - - char *wvc_url = g_strconcat(uri, "c", nullptr); - - InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond, - IgnoreError()); - g_free(wvc_url); - - if (is_wvc == nullptr) - return nullptr; - - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - char first_byte; - size_t nbytes = decoder_read(decoder, *is_wvc, - &first_byte, sizeof(first_byte)); - if (nbytes == 0) { - is_wvc->Close(); - return nullptr; - } - - /* 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(Decoder &decoder, InputStream &is) -{ - int open_flags = OPEN_NORMALIZE; - bool can_seek = is.seekable; - - wavpack_input isp_wvc; - InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(), - is.mutex, is.cond, - &isp_wvc); - if (is_wvc != nullptr) { - open_flags |= OPEN_WVC; - can_seek &= is_wvc->seekable; - } - - if (!can_seek) { - open_flags |= OPEN_STREAMING; - } - - wavpack_input isp; - wavpack_input_init(&isp, decoder, is); - - char error[ERRORLEN]; - WavpackContext *wpc = - WavpackOpenFileInputEx(&mpd_is_reader, &isp, - open_flags & OPEN_WVC - ? &isp_wvc : nullptr, - error, open_flags, 23); - - if (wpc == nullptr) { - FormatError(wavpack_domain, - "failed to open WavPack stream: %s", error); - return; - } - - wavpack_decode(decoder, wpc, can_seek); - - WavpackCloseFile(wpc); - if (open_flags & OPEN_WVC) { - is_wvc->Close(); - } -} - -/* - * Decodes a file. - */ -static void -wavpack_filedecode(Decoder &decoder, const char *fname) -{ - char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, - 23); - if (wpc == nullptr) { - FormatWarning(wavpack_domain, - "failed to open WavPack file \"%s\": %s", - fname, error); - return; - } - - ReplayGainInfo rgi; - if (wavpack_replaygain(rgi, wpc)) - decoder_replay_gain(decoder, &rgi); - - wavpack_decode(decoder, wpc, true); - - WavpackCloseFile(wpc); -} - -static char const *const wavpack_suffixes[] = { - "wv", - nullptr -}; - -static char const *const wavpack_mime_types[] = { - "audio/x-wavpack", - nullptr -}; - -const struct DecoderPlugin wavpack_decoder_plugin = { - "wavpack", - nullptr, - nullptr, - wavpack_streamdecode, - wavpack_filedecode, - wavpack_scan_file, - nullptr, - nullptr, - wavpack_suffixes, - wavpack_mime_types -}; diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/WavpackDecoderPlugin.hxx deleted file mode 100644 index 3a2d94532..000000000 --- a/src/decoder/WavpackDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_WAVPACK_HXX -#define MPD_DECODER_WAVPACK_HXX - -extern const struct DecoderPlugin wavpack_decoder_plugin; - -#endif diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx deleted file mode 100644 index 3da3f1387..000000000 --- a/src/decoder/WildmidiDecoderPlugin.cxx +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2003-2013 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 "WildmidiDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "system/FatalError.hxx" -#include "Log.hxx" - -extern "C" { -#include <wildmidi_lib.h> -} - -static constexpr Domain wildmidi_domain("wildmidi"); - -static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000; - -static bool -wildmidi_init(const config_param ¶m) -{ - Error error; - const AllocatedPath path = - param.GetBlockPath("config_file", - "/etc/timidity/timidity.cfg", - error); - if (path.IsNull()) - FatalError(error); - - if (!FileExists(path)) { - const auto utf8 = path.ToUTF8(); - FormatDebug(wildmidi_domain, - "configuration file does not exist: %s", - utf8.c_str()); - return false; - } - - return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0; -} - -static void -wildmidi_finish(void) -{ - WildMidi_Shutdown(); -} - -static void -wildmidi_file_decode(Decoder &decoder, const char *path_fs) -{ - static constexpr AudioFormat audio_format = { - WILDMIDI_SAMPLE_RATE, - SampleFormat::S16, - 2, - }; - midi *wm; - const struct _WM_Info *info; - - wm = WildMidi_Open(path_fs); - if (wm == nullptr) - return; - - info = WildMidi_GetInfo(wm); - if (info == nullptr) { - WildMidi_Close(wm); - return; - } - - decoder_initialized(decoder, audio_format, true, - info->approx_total_samples / WILDMIDI_SAMPLE_RATE); - - DecoderCommand cmd; - do { - char buffer[4096]; - int len; - - info = WildMidi_GetInfo(wm); - if (info == nullptr) - break; - - len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); - if (len <= 0) - break; - - cmd = decoder_data(decoder, nullptr, buffer, len, 0); - - if (cmd == DecoderCommand::SEEK) { - unsigned long seek_where = WILDMIDI_SAMPLE_RATE * - decoder_seek_where(decoder); - - WildMidi_FastSeek(wm, &seek_where); - decoder_command_finished(decoder); - cmd = DecoderCommand::NONE; - } - - } while (cmd == DecoderCommand::NONE); - - WildMidi_Close(wm); -} - -static bool -wildmidi_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - midi *wm = WildMidi_Open(path_fs); - if (wm == nullptr) - return false; - - const struct _WM_Info *info = WildMidi_GetInfo(wm); - if (info == nullptr) { - WildMidi_Close(wm); - return false; - } - - int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; - tag_handler_invoke_duration(handler, handler_ctx, duration); - - WildMidi_Close(wm); - - return true; -} - -static const char *const wildmidi_suffixes[] = { - "mid", - nullptr -}; - -const struct DecoderPlugin wildmidi_decoder_plugin = { - "wildmidi", - wildmidi_init, - wildmidi_finish, - nullptr, - wildmidi_file_decode, - wildmidi_scan_file, - nullptr, - nullptr, - wildmidi_suffixes, - nullptr, -}; diff --git a/src/decoder/WildmidiDecoderPlugin.hxx b/src/decoder/WildmidiDecoderPlugin.hxx deleted file mode 100644 index a6289612e..000000000 --- a/src/decoder/WildmidiDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_WILDMIDI_HXX -#define MPD_DECODER_WILDMIDI_HXX - -extern const struct DecoderPlugin wildmidi_decoder_plugin; - -#endif diff --git a/src/decoder/XiphTags.cxx b/src/decoder/XiphTags.cxx deleted file mode 100644 index b9958a19a..000000000 --- a/src/decoder/XiphTags.cxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 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.hxx" - -const struct tag_table xiph_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { nullptr, TAG_NUM_OF_ITEM_TYPES } -}; diff --git a/src/decoder/XiphTags.hxx b/src/decoder/XiphTags.hxx deleted file mode 100644 index 606dfef10..000000000 --- a/src/decoder/XiphTags.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 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_HXX -#define MPD_XIPH_TAGS_HXX - -#include "check.h" -#include "tag/TagTable.hxx" - -extern const struct tag_table xiph_tags[]; - -#endif diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.cxx b/src/decoder/plugins/AdPlugDecoderPlugin.cxx new file mode 100644 index 000000000..9cc37ade4 --- /dev/null +++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2014 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/TagHandler.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <adplug/adplug.h> +#include <adplug/emuopl.h> + +#include <assert.h> + +static constexpr Domain adplug_domain("adplug"); + +static unsigned sample_rate; + +static bool +adplug_init(const config_param ¶m) +{ + FormatDebug(adplug_domain, "adplug %s", + CAdPlug::get_version().c_str()); + + Error error; + + sample_rate = param.GetBlockValue("sample_rate", 48000u); + if (!audio_check_sample_rate(sample_rate, error)) { + LogError(error); + return false; + } + + return true; +} + +static void +adplug_file_decode(Decoder &decoder, Path path_fs) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl); + if (player == nullptr) + return; + + const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, false, + SongTime::FromMS(player->songlength())); + + DecoderCommand cmd; + + do { + if (!player->update()) + break; + + int16_t buffer[2048]; + constexpr unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2; + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, nullptr, + buffer, sizeof(buffer), + 0); + } while (cmd == DecoderCommand::NONE); + + delete player; +} + +static void +adplug_scan_tag(TagType 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(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl); + if (player == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + SongTime::FromMS(player->songlength())); + + 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 DecoderPlugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.h b/src/decoder/plugins/AdPlugDecoderPlugin.h new file mode 100644 index 000000000..539dbbf0a --- /dev/null +++ b/src/decoder/plugins/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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 DecoderPlugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx new file mode 100644 index 000000000..a0ef71e49 --- /dev/null +++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2003-2014 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 "AudiofileDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <audiofile.h> +#include <af_vfs.h> + +#include <assert.h> +#include <stdio.h> + +static constexpr Domain audiofile_domain("audiofile"); + +static void +audiofile_error_func(long, const char *msg) +{ + LogWarning(audiofile_domain, msg); +} + +static bool +audiofile_init(const config_param &) +{ + afSetErrorHandler(audiofile_error_func); + return true; +} + +struct AudioFileInputStream { + Decoder *const decoder; + InputStream &is; + + size_t Read(void *buffer, size_t size) { + /* libaudiofile does not like partial reads at all, + and will abort playback; therefore always force full + reads */ + return decoder_read_full(decoder, is, buffer, size) + ? size + : 0; + } +}; + +gcc_pure +static SongTime +audiofile_get_duration(AFfilehandle fh) +{ + return SongTime::FromScale<uint64_t>(afGetFrameCount(fh, AF_DEFAULT_TRACK), + afGetRate(fh, AF_DEFAULT_TRACK)); +} + +static ssize_t +audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + + return afis.Read(data, length); +} + +static AFfileoffset +audiofile_file_length(AFvirtualfile *vfile) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + InputStream &is = afis.is; + + return is.GetSize(); +} + +static AFfileoffset +audiofile_file_tell(AFvirtualfile *vfile) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + InputStream &is = afis.is; + + return is.GetOffset(); +} + +static void +audiofile_file_destroy(AFvirtualfile *vfile) +{ + assert(vfile->closure != nullptr); + + vfile->closure = nullptr; +} + +static AFfileoffset +audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset _offset, + int is_relative) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + InputStream &is = afis.is; + + offset_type offset = _offset; + if (is_relative) + offset += is.GetOffset(); + + Error error; + if (is.LockSeek(offset, error)) { + return is.GetOffset(); + } else { + LogError(error, "Seek failed"); + return -1; + } +} + +static AFvirtualfile * +setup_virtual_fops(AudioFileInputStream &afis) +{ + AFvirtualfile *vf = new AFvirtualfile(); + vf->closure = &afis; + vf->write = nullptr; + 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 SampleFormat +audiofile_bits_to_sample_format(int bits) +{ + switch (bits) { + case 8: + return SampleFormat::S8; + + case 16: + return SampleFormat::S16; + + case 24: + return SampleFormat::S24_P32; + + case 32: + return SampleFormat::S32; + } + + return SampleFormat::UNDEFINED; +} + +static SampleFormat +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))) { + FormatDebug(audiofile_domain, + "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(Decoder &decoder, InputStream &is) +{ + if (!is.IsSeekable() || !is.KnownSize()) { + LogWarning(audiofile_domain, "not seekable"); + return; + } + + AudioFileInputStream afis{&decoder, is}; + AFvirtualfile *const vf = setup_virtual_fops(afis); + + const AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr); + if (fh == AF_NULL_FILEHANDLE) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + afGetRate(fh, AF_DEFAULT_TRACK), + audiofile_setup_sample_format(fh), + afGetVirtualChannels(fh, AF_DEFAULT_TRACK), + error)) { + LogError(error); + afCloseFile(fh); + return; + } + + const auto total_time = audiofile_get_duration(fh); + + const uint16_t kbit_rate = (uint16_t) + (is.GetSize() * uint64_t(8) / total_time.ToMS()); + + const unsigned frame_size = (unsigned) + afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true); + + decoder_initialized(decoder, audio_format, true, total_time); + + DecoderCommand cmd; + do { + /* pick 1020 since its divisible for 8,16,24, and + 32-bit audio */ + char chunk[1020]; + const int nframes = + afReadFrames(fh, AF_DEFAULT_TRACK, chunk, + sizeof(chunk) / frame_size); + if (nframes <= 0) + break; + + cmd = decoder_data(decoder, nullptr, + chunk, nframes * frame_size, + kbit_rate); + + if (cmd == DecoderCommand::SEEK) { + AFframecount frame = decoder_seek_where_frame(decoder); + afSeekFrame(fh, AF_DEFAULT_TRACK, frame); + + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + afCloseFile(fh); +} + +gcc_pure +static SignedSongTime +audiofile_get_duration(InputStream &is) +{ + if (!is.IsSeekable() || !is.KnownSize()) + return SignedSongTime::Negative(); + + AudioFileInputStream afis{nullptr, is}; + AFvirtualfile *vf = setup_virtual_fops(afis); + AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr); + if (fh == AF_NULL_FILEHANDLE) + return SignedSongTime::Negative(); + + const auto duration = audiofile_get_duration(fh); + afCloseFile(fh); + return duration; +} + +static bool +audiofile_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + const auto duration = audiofile_get_duration(is); + if (duration.IsNegative()) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); + return true; +} + +static const char *const audiofile_suffixes[] = { + "wav", "au", "aiff", "aif", nullptr +}; + +static const char *const audiofile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + nullptr +}; + +const struct DecoderPlugin audiofile_decoder_plugin = { + "audiofile", + audiofile_init, + nullptr, + audiofile_stream_decode, + nullptr, + nullptr, + audiofile_scan_stream, + nullptr, + audiofile_suffixes, + audiofile_mime_types, +}; diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.hxx b/src/decoder/plugins/AudiofileDecoderPlugin.hxx new file mode 100644 index 000000000..61129076d --- /dev/null +++ b/src/decoder/plugins/AudiofileDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_AUDIOFILE_HXX +#define MPD_DECODER_AUDIOFILE_HXX + +extern const struct DecoderPlugin audiofile_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx new file mode 100644 index 000000000..8892ed387 --- /dev/null +++ b/src/decoder/plugins/DsdLib.cxx @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* \file + * + * This file contains functions used by the DSF and DSDIFF decoders. + * + */ + +#include "config.h" +#include "DsdLib.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "tag/TagId3.hxx" +#include "util/Error.hxx" + +#include <string.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +bool +DsdId::Equals(const char *s) const +{ + assert(s != nullptr); + assert(strlen(s) == sizeof(value)); + + return memcmp(value, s, sizeof(value)) == 0; +} + +/** + * Skip the #input_stream to the specified offset. + */ +bool +dsdlib_skip_to(Decoder *decoder, InputStream &is, + offset_type offset) +{ + if (is.IsSeekable()) + return is.LockSeek(offset, IgnoreError()); + + if (is.GetOffset() > offset) + return false; + + return dsdlib_skip(decoder, is, offset - is.GetOffset()); +} + +/** + * Skip some bytes from the #input_stream. + */ +bool +dsdlib_skip(Decoder *decoder, InputStream &is, + offset_type delta) +{ + if (delta == 0) + return true; + + if (is.IsSeekable()) + return is.LockSeek(is.GetOffset() + delta, IgnoreError()); + + if (delta > 1024 * 1024) + /* don't skip more than one megabyte; it would be too + expensive */ + return false; + + return decoder_skip(decoder, is, delta); +} + +bool +dsdlib_valid_freq(uint32_t samplefreq) +{ + switch (samplefreq) { + case 2822400: /* DSD64, 64xFs, Fs = 44.100kHz */ + case 3072000: /* DSD64 with Fs = 48.000 kHz */ + case 5644800: + case 6144000: + case 11289600: + case 12288000: + case 22579200:/* DSD512 */ + case 24576000: + return true; + + default: + return false; + } +} + +#ifdef HAVE_ID3TAG +void +dsdlib_tag_id3(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, int64_t tagoffset) +{ + assert(tagoffset >= 0); + + if (tagoffset == 0 || !is.KnownSize()) + return; + + if (!dsdlib_skip_to(nullptr, is, tagoffset)) + return; + + /* Prevent broken files causing problems */ + const auto size = is.GetSize(); + const auto offset = is.GetOffset(); + if (offset >= size) + return; + + const id3_length_t count = size - offset; + + /* Check and limit id3 tag size to prevent a stack overflow */ + id3_byte_t dsdid3[4096]; + if (count == 0 || count > sizeof(dsdid3)) + return; + + if (!decoder_read_full(nullptr, is, dsdid3, count)) + return; + + struct id3_tag *id3_tag = id3_tag_parse(dsdid3, count); + if (id3_tag == nullptr) + return; + + scan_id3_tag(id3_tag, handler, handler_ctx); + + id3_tag_delete(id3_tag); + + return; +} +#endif diff --git a/src/decoder/plugins/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx new file mode 100644 index 000000000..8295bcbf6 --- /dev/null +++ b/src/decoder/plugins/DsdLib.hxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2003-2014 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_DSDLIB_HXX +#define MPD_DECODER_DSDLIB_HXX + +#include "system/ByteOrder.hxx" +#include "input/Offset.hxx" +#include "Compiler.h" + +#include <stddef.h> +#include <stdint.h> + +struct Decoder; +class InputStream; + +struct DsdId { + char value[4]; + + gcc_pure + bool Equals(const char *s) const; +}; + +class DsdUint64 { + uint32_t lo; + uint32_t hi; + +public: + constexpr uint64_t Read() const { + return (uint64_t(FromLE32(hi)) << 32) | + uint64_t(FromLE32(lo)); + } +}; + +class DffDsdUint64 { + uint32_t hi; + uint32_t lo; + +public: + constexpr uint64_t Read() const { + return (uint64_t(FromBE32(hi)) << 32) | + uint64_t(FromBE32(lo)); + } +}; + +bool +dsdlib_skip_to(Decoder *decoder, InputStream &is, + offset_type offset); + +bool +dsdlib_skip(Decoder *decoder, InputStream &is, + offset_type delta); + +/** + * Check if the sample frequency is a valid DSD frequency. + **/ +gcc_const +bool +dsdlib_valid_freq(uint32_t samplefreq); + +/** + * Add tags from ID3 tag. All tags commonly found in the ID3 tags of + * DSF and DSDIFF files are imported + */ +void +dsdlib_tag_id3(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, int64_t tagoffset); + +#endif diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx new file mode 100644 index 000000000..b6c79e11e --- /dev/null +++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* \file + * + * This plugin decodes DSDIFF data (SACD) embedded in DFF files. + * The DFF code was modeled after the specification found here: + * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf + * + * All functions common to both DSD decoders have been moved to dsdlib + */ + +#include "config.h" +#include "DsdiffDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "system/ByteOrder.hxx" +#include "tag/TagHandler.hxx" +#include "DsdLib.hxx" +#include "Log.hxx" + +struct DsdiffHeader { + DsdId id; + DffDsdUint64 size; + DsdId format; +}; + +struct DsdiffChunkHeader { + DsdId id; + DffDsdUint64 size; + + /** + * Read the "size" attribute from the specified header, converting it + * to the host byte order if needed. + */ + constexpr + uint64_t GetSize() const { + return size.Read(); + } +}; + +/** struct for DSDIFF native Artist and Title tags */ +struct dsdiff_native_tag { + uint32_t size; +}; + +struct DsdiffMetaData { + unsigned sample_rate, channels; + bool bitreverse; + offset_type chunk_size; +}; + +static bool lsbitfirst; + +static bool +dsdiff_init(const config_param ¶m) +{ + lsbitfirst = param.GetBlockValue("lsbitfirst", false); + return true; +} + +static bool +dsdiff_read_id(Decoder *decoder, InputStream &is, + DsdId *id) +{ + return decoder_read_full(decoder, is, id, sizeof(*id)); +} + +static bool +dsdiff_read_chunk_header(Decoder *decoder, InputStream &is, + DsdiffChunkHeader *header) +{ + return decoder_read_full(decoder, is, header, sizeof(*header)); +} + +static bool +dsdiff_read_payload(Decoder *decoder, InputStream &is, + const DsdiffChunkHeader *header, + void *data, size_t length) +{ + uint64_t size = header->GetSize(); + if (size != (uint64_t)length) + return false; + + return decoder_read_full(decoder, is, data, length); +} + +/** + * Read and parse a "SND" chunk inside "PROP". + */ +static bool +dsdiff_read_prop_snd(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + offset_type end_offset) +{ + DsdiffChunkHeader header; + while (is.GetOffset() + sizeof(header) <= end_offset) { + if (!dsdiff_read_chunk_header(decoder, is, &header)) + return false; + + offset_type chunk_end_offset = is.GetOffset() + + header.GetSize(); + if (chunk_end_offset > end_offset) + return false; + + if (header.id.Equals("FS ")) { + uint32_t sample_rate; + if (!dsdiff_read_payload(decoder, is, &header, + &sample_rate, + sizeof(sample_rate))) + return false; + + metadata->sample_rate = FromBE32(sample_rate); + } else if (header.id.Equals("CHNL")) { + uint16_t channels; + if (header.GetSize() < sizeof(channels) || + !decoder_read_full(decoder, is, + &channels, sizeof(channels)) || + !dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + + metadata->channels = FromBE16(channels); + } else if (header.id.Equals("CMPR")) { + DsdId type; + if (header.GetSize() < sizeof(type) || + !decoder_read_full(decoder, is, + &type, sizeof(type)) || + !dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + + if (!type.Equals("DSD ")) + /* only uncompressed DSD audio data + is implemented */ + return false; + } else { + /* ignore unknown chunk */ + + if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } + + return is.GetOffset() == end_offset; +} + +/** + * Read and parse a "PROP" chunk. + */ +static bool +dsdiff_read_prop(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + const DsdiffChunkHeader *prop_header) +{ + uint64_t prop_size = prop_header->GetSize(); + const offset_type end_offset = is.GetOffset() + prop_size; + + DsdId prop_id; + if (prop_size < sizeof(prop_id) || + !dsdiff_read_id(decoder, is, &prop_id)) + return false; + + if (prop_id.Equals("SND ")) + return dsdiff_read_prop_snd(decoder, is, metadata, end_offset); + else + /* ignore unknown PROP chunk */ + return dsdlib_skip_to(decoder, is, end_offset); +} + +static void +dsdiff_handle_native_tag(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, offset_type tagoffset, + TagType type) +{ + if (!dsdlib_skip_to(nullptr, is, tagoffset)) + return; + + struct dsdiff_native_tag metatag; + + if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag))) + return; + + uint32_t length = FromBE32(metatag.size); + + /* Check and limit size of the tag to prevent a stack overflow */ + if (length == 0 || length > 60) + return; + + char string[length]; + char *label; + label = string; + + if (!decoder_read_full(nullptr, is, label, (size_t)length)) + return; + + string[length] = '\0'; + tag_handler_invoke_tag(handler, handler_ctx, type, label); + return; +} + +/** + * Read and parse additional metadata chunks for tagging purposes. By default + * dsdiff files only support equivalents for artist and title but some of the + * extract tools add an id3 tag to provide more tags. If such id3 is found + * this will be used for tagging otherwise the native tags (if any) will be + * used + */ + +static bool +dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header, + const struct tag_handler *handler, + void *handler_ctx) +{ + + /* skip from DSD data to next chunk header */ + if (!dsdlib_skip(decoder, is, metadata->chunk_size)) + return false; + if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) + return false; + + /** offset for artist tag */ + offset_type artist_offset = 0; + /** offset for title tag */ + offset_type title_offset = 0; + +#ifdef HAVE_ID3TAG + offset_type id3_offset = 0; +#endif + + /* Now process all the remaining chunk headers in the stream + and record their position and size */ + + do { + offset_type chunk_size = chunk_header->GetSize(); + + /* DIIN chunk, is directly followed by other chunks */ + if (chunk_header->id.Equals("DIIN")) + chunk_size = 0; + + /* DIAR chunk - DSDIFF native tag for Artist */ + if (chunk_header->id.Equals("DIAR")) { + chunk_size = chunk_header->GetSize(); + artist_offset = is.GetOffset(); + } + + /* DITI chunk - DSDIFF native tag for Title */ + if (chunk_header->id.Equals("DITI")) { + chunk_size = chunk_header->GetSize(); + title_offset = is.GetOffset(); + } +#ifdef HAVE_ID3TAG + /* 'ID3 ' chunk, offspec. Used by sacdextract */ + if (chunk_header->id.Equals("ID3 ")) { + chunk_size = chunk_header->GetSize(); + id3_offset = is.GetOffset(); + } +#endif + + if (!dsdlib_skip(decoder, is, chunk_size)) + break; + } while (dsdiff_read_chunk_header(decoder, is, chunk_header)); + + /* done processing chunk headers, process tags if any */ + +#ifdef HAVE_ID3TAG + if (id3_offset != 0) { + /* a ID3 tag has preference over the other tags, do not process + other tags if we have one */ + dsdlib_tag_id3(is, handler, handler_ctx, id3_offset); + return true; + } +#endif + + if (artist_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + artist_offset, TAG_ARTIST); + + if (title_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + title_offset, TAG_TITLE); + return true; +} + +/** + * Read and parse all metadata chunks at the beginning. Stop when the + * first "DSD" chunk is seen, and return its header in the + * "chunk_header" parameter. + */ +static bool +dsdiff_read_metadata(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header) +{ + DsdiffHeader header; + if (!decoder_read_full(decoder, is, &header, sizeof(header)) || + !header.id.Equals("FRM8") || + !header.format.Equals("DSD ")) + return false; + + while (true) { + if (!dsdiff_read_chunk_header(decoder, is, + chunk_header)) + return false; + + if (chunk_header->id.Equals("PROP")) { + if (!dsdiff_read_prop(decoder, is, metadata, + chunk_header)) + return false; + } else if (chunk_header->id.Equals("DSD ")) { + const offset_type chunk_size = chunk_header->GetSize(); + metadata->chunk_size = chunk_size; + return true; + } else { + /* ignore unknown chunk */ + const offset_type chunk_size = chunk_header->GetSize(); + const offset_type chunk_end_offset = + is.GetOffset() + chunk_size; + + if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } +} + +static void +bit_reverse_buffer(uint8_t *p, uint8_t *end) +{ + for (; p < end; ++p) + *p = bit_reverse(*p); +} + +static offset_type +FrameToOffset(uint64_t frame, unsigned channels) +{ + return frame * channels; +} + +/** + * Decode one "DSD" chunk. + */ +static bool +dsdiff_decode_chunk(Decoder &decoder, InputStream &is, + unsigned channels, unsigned sample_rate, + const offset_type total_bytes) +{ + const offset_type start_offset = is.GetOffset(); + + uint8_t buffer[8192]; + + const size_t sample_size = sizeof(buffer[0]); + const size_t frame_size = channels * sample_size; + const unsigned buffer_frames = sizeof(buffer) / frame_size; + const size_t buffer_size = buffer_frames * frame_size; + + auto cmd = decoder_get_command(decoder); + for (offset_type remaining_bytes = total_bytes; + remaining_bytes >= frame_size && cmd != DecoderCommand::STOP;) { + if (cmd == DecoderCommand::SEEK) { + uint64_t frame = decoder_seek_where_frame(decoder); + offset_type offset = FrameToOffset(frame, channels); + if (offset >= total_bytes) { + decoder_command_finished(decoder); + break; + } + + if (dsdlib_skip_to(&decoder, is, + start_offset + offset)) { + decoder_command_finished(decoder); + remaining_bytes = total_bytes - offset; + } else + decoder_seek_error(decoder); + } + + /* see how much aligned data from the remaining chunk + fits into the local buffer */ + size_t now_size = buffer_size; + if (remaining_bytes < (offset_type)now_size) { + unsigned now_frames = remaining_bytes / frame_size; + now_size = now_frames * frame_size; + } + + if (!decoder_read_full(&decoder, is, buffer, now_size)) + return false; + + const size_t nbytes = now_size; + remaining_bytes -= nbytes; + + if (lsbitfirst) + bit_reverse_buffer(buffer, buffer + nbytes); + + cmd = decoder_data(decoder, is, buffer, nbytes, + sample_rate / 1000); + } + + return true; +} + +static void +dsdiff_stream_decode(Decoder &decoder, InputStream &is) +{ + DsdiffMetaData metadata; + + DsdiffChunkHeader chunk_header; + /* check if it is is a proper DFF file */ + if (!dsdiff_read_metadata(&decoder, is, &metadata, &chunk_header)) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, error)) { + LogError(error); + return; + } + + /* calculate song time from DSD chunk size and sample frequency */ + offset_type chunk_size = metadata.chunk_size; + + uint64_t n_frames = chunk_size / audio_format.channels; + auto songtime = SongTime::FromScale<uint64_t>(n_frames, + audio_format.sample_rate); + + /* success: file was recognized */ + decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime); + + /* every iteration of the following loop decodes one "DSD" + chunk from a DFF file */ + + dsdiff_decode_chunk(decoder, is, + metadata.channels, + metadata.sample_rate, + chunk_size); +} + +static bool +dsdiff_scan_stream(InputStream &is, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + DsdiffMetaData metadata; + DsdiffChunkHeader chunk_header; + + /* First check for DFF metadata */ + if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header)) + return false; + + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, IgnoreError())) + /* refuse to parse files which we cannot play anyway */ + return false; + + /* calculate song time and add as tag */ + uint64_t n_frames = metadata.chunk_size / audio_format.channels; + auto songtime = SongTime::FromScale<uint64_t>(n_frames, + audio_format.sample_rate); + tag_handler_invoke_duration(handler, handler_ctx, songtime); + + /* Read additional metadata and created tags if available */ + dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header, + handler, handler_ctx); + + return true; +} + +static const char *const dsdiff_suffixes[] = { + "dff", + nullptr +}; + +static const char *const dsdiff_mime_types[] = { + "application/x-dff", + nullptr +}; + +const struct DecoderPlugin dsdiff_decoder_plugin = { + "dsdiff", + dsdiff_init, + nullptr, + dsdiff_stream_decode, + nullptr, + nullptr, + dsdiff_scan_stream, + nullptr, + dsdiff_suffixes, + dsdiff_mime_types, +}; diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.hxx b/src/decoder/plugins/DsdiffDecoderPlugin.hxx new file mode 100644 index 000000000..7aa36752b --- /dev/null +++ b/src/decoder/plugins/DsdiffDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_DSDIFF_H +#define MPD_DECODER_DSDIFF_H + +extern const struct DecoderPlugin dsdiff_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx new file mode 100644 index 000000000..690616d15 --- /dev/null +++ b/src/decoder/plugins/DsfDecoderPlugin.cxx @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* \file + * + * This plugin decodes DSDIFF data (SACD) embedded in DSF files. + * + * The DSF code was created using the specification found here: + * http://dsd-guide.com/sonys-dsf-file-format-spec + * + * All functions common to both DSD decoders have been moved to dsdlib + */ + +#include "config.h" +#include "DsfDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "system/ByteOrder.hxx" +#include "DsdLib.hxx" +#include "tag/TagHandler.hxx" +#include "Log.hxx" + +#include <string.h> + +static constexpr unsigned DSF_BLOCK_SIZE = 4096; + +struct DsfMetaData { + unsigned sample_rate, channels; + bool bitreverse; + offset_type n_blocks; +#ifdef HAVE_ID3TAG + offset_type id3_offset; +#endif +}; + +struct DsfHeader { + /** DSF header id: "DSD " */ + DsdId id; + /** DSD chunk size, including id = 28 */ + DsdUint64 size; + /** total file size */ + DsdUint64 fsize; + /** pointer to id3v2 metadata, should be at the end of the file */ + DsdUint64 pmeta; +}; + +/** DSF file fmt chunk */ +struct DsfFmtChunk { + /** id: "fmt " */ + DsdId id; + /** fmt chunk size, including id, normally 52 */ + DsdUint64 size; + /** version of this format = 1 */ + uint32_t version; + /** 0: DSD raw */ + uint32_t formatid; + /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */ + uint32_t channeltype; + /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */ + uint32_t channelnum; + /** sample frequency: 2822400, 5644800 */ + uint32_t sample_freq; + /** bits per sample 1 or 8 */ + uint32_t bitssample; + /** Sample count per channel in bytes */ + DsdUint64 scnt; + /** block size per channel = 4096 */ + uint32_t block_size; + /** reserved, should be all zero */ + uint32_t reserved; +}; + +struct DsfDataChunk { + DsdId id; + /** "data" chunk size, includes header (id+size) */ + DsdUint64 size; +}; + +/** + * Read and parse all needed metadata chunks for DSF files. + */ +static bool +dsf_read_metadata(Decoder *decoder, InputStream &is, + DsfMetaData *metadata) +{ + DsfHeader dsf_header; + if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) || + !dsf_header.id.Equals("DSD ")) + return false; + + const offset_type chunk_size = dsf_header.size.Read(); + if (sizeof(dsf_header) != chunk_size) + return false; + +#ifdef HAVE_ID3TAG + const offset_type metadata_offset = dsf_header.pmeta.Read(); +#endif + + /* read the 'fmt ' chunk of the DSF file */ + DsfFmtChunk dsf_fmt_chunk; + if (!decoder_read_full(decoder, is, + &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || + !dsf_fmt_chunk.id.Equals("fmt ")) + return false; + + const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read(); + if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) + return false; + + uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq); + const unsigned channels = FromLE32(dsf_fmt_chunk.channelnum); + + /* for now, only support version 1 of the standard, DSD raw stereo + files with a sample freq of 2822400 or 5644800 Hz */ + + if (FromLE32(dsf_fmt_chunk.version) != 1 || + FromLE32(dsf_fmt_chunk.formatid) != 0 || + !audio_valid_channel_count(channels) || + !dsdlib_valid_freq(samplefreq)) + return false; + + uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size); + /* according to the spec block size should always be 4096 */ + if (chblksize != DSF_BLOCK_SIZE) + return false; + + /* read the 'data' chunk of the DSF file */ + DsfDataChunk data_chunk; + if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) || + !data_chunk.id.Equals("data")) + return false; + + /* data size of DSF files are padded to multiple of 4096, + we use the actual data size as chunk size */ + + offset_type data_size = data_chunk.size.Read(); + if (data_size < sizeof(data_chunk)) + return false; + + data_size -= sizeof(data_chunk); + + /* data_size cannot be bigger or equal to total file size */ + if (is.KnownSize() && data_size > is.GetRest()) + return false; + + /* use the sample count from the DSF header as the upper + bound, because some DSF files contain junk at the end of + the "data" chunk */ + const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read(); + const offset_type playable_size = samplecnt * channels / 8; + if (data_size > playable_size) + data_size = playable_size; + + const size_t block_size = channels * DSF_BLOCK_SIZE; + metadata->n_blocks = data_size / block_size; + metadata->channels = channels; + metadata->sample_rate = samplefreq; +#ifdef HAVE_ID3TAG + metadata->id3_offset = metadata_offset; +#endif + /* check bits per sample format, determine if bitreverse is needed */ + metadata->bitreverse = FromLE32(dsf_fmt_chunk.bitssample) == 1; + return true; +} + +static void +bit_reverse_buffer(uint8_t *p, uint8_t *end) +{ + for (; p < end; ++p) + *p = bit_reverse(*p); +} + +static void +InterleaveDsfBlockMono(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src) +{ + memcpy(dest, src, DSF_BLOCK_SIZE); +} + +/** + * DSF data is build up of alternating 4096 blocks of DSD samples for left and + * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1 + * block of 4096 DSD right samples to 8k of samples in normal PCM left/right + * order. + */ +static void +InterleaveDsfBlockStereo(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src) +{ + for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i) { + dest[2 * i] = src[i]; + dest[2 * i + 1] = src[DSF_BLOCK_SIZE + i]; + } +} + +static void +InterleaveDsfBlockChannel(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src, + unsigned channels) +{ + for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i, dest += channels, ++src) + *dest = *src; +} + +static void +InterleaveDsfBlockGeneric(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src, + unsigned channels) +{ + for (unsigned c = 0; c < channels; ++c, ++dest, src += DSF_BLOCK_SIZE) + InterleaveDsfBlockChannel(dest, src, channels); +} + +static void +InterleaveDsfBlock(uint8_t *gcc_restrict dest, const uint8_t *gcc_restrict src, + unsigned channels) +{ + if (channels == 1) + InterleaveDsfBlockMono(dest, src); + else if (channels == 2) + InterleaveDsfBlockStereo(dest, src); + else + InterleaveDsfBlockGeneric(dest, src, channels); +} + +static offset_type +FrameToBlock(uint64_t frame) +{ + return frame / DSF_BLOCK_SIZE; +} + +/** + * Decode one complete DSF 'data' chunk i.e. a complete song + */ +static bool +dsf_decode_chunk(Decoder &decoder, InputStream &is, + unsigned channels, unsigned sample_rate, + offset_type n_blocks, + bool bitreverse) +{ + const size_t block_size = channels * DSF_BLOCK_SIZE; + const offset_type start_offset = is.GetOffset(); + + auto cmd = decoder_get_command(decoder); + for (offset_type i = 0; i < n_blocks && cmd != DecoderCommand::STOP;) { + if (cmd == DecoderCommand::SEEK) { + uint64_t frame = decoder_seek_where_frame(decoder); + offset_type block = FrameToBlock(frame); + if (block >= n_blocks) { + decoder_command_finished(decoder); + break; + } + + offset_type offset = + start_offset + block * block_size; + if (dsdlib_skip_to(&decoder, is, offset)) { + decoder_command_finished(decoder); + i = block; + } else + decoder_seek_error(decoder); + } + + /* worst-case buffer size */ + uint8_t buffer[MAX_CHANNELS * DSF_BLOCK_SIZE]; + if (!decoder_read_full(&decoder, is, buffer, block_size)) + return false; + + if (bitreverse) + bit_reverse_buffer(buffer, buffer + block_size); + + uint8_t interleaved_buffer[MAX_CHANNELS * DSF_BLOCK_SIZE]; + InterleaveDsfBlock(interleaved_buffer, buffer, channels); + + cmd = decoder_data(decoder, is, + interleaved_buffer, block_size, + sample_rate / 1000); + ++i; + } + + return true; +} + +static void +dsf_stream_decode(Decoder &decoder, InputStream &is) +{ + /* check if it is a proper DSF file */ + DsfMetaData metadata; + if (!dsf_read_metadata(&decoder, is, &metadata)) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, error)) { + LogError(error); + return; + } + /* Calculate song time from DSD chunk size and sample frequency */ + const auto n_blocks = metadata.n_blocks; + auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE, + audio_format.sample_rate); + + /* success: file was recognized */ + decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime); + + dsf_decode_chunk(decoder, is, metadata.channels, + metadata.sample_rate, + n_blocks, + metadata.bitreverse); +} + +static bool +dsf_scan_stream(InputStream &is, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + /* check DSF metadata */ + DsfMetaData metadata; + if (!dsf_read_metadata(nullptr, is, &metadata)) + return false; + + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, IgnoreError())) + /* refuse to parse files which we cannot play anyway */ + return false; + + /* calculate song time and add as tag */ + const auto n_blocks = metadata.n_blocks; + auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE, + audio_format.sample_rate); + tag_handler_invoke_duration(handler, handler_ctx, songtime); + +#ifdef HAVE_ID3TAG + /* Add available tags from the ID3 tag */ + dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset); +#endif + return true; +} + +static const char *const dsf_suffixes[] = { + "dsf", + nullptr +}; + +static const char *const dsf_mime_types[] = { + "application/x-dsf", + nullptr +}; + +const struct DecoderPlugin dsf_decoder_plugin = { + "dsf", + nullptr, + nullptr, + dsf_stream_decode, + nullptr, + nullptr, + dsf_scan_stream, + nullptr, + dsf_suffixes, + dsf_mime_types, +}; diff --git a/src/decoder/plugins/DsfDecoderPlugin.hxx b/src/decoder/plugins/DsfDecoderPlugin.hxx new file mode 100644 index 000000000..02bea0b5c --- /dev/null +++ b/src/decoder/plugins/DsfDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_DSF_H +#define MPD_DECODER_DSF_H + +extern const struct DecoderPlugin dsf_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx new file mode 100644 index 000000000..add23aaa4 --- /dev/null +++ b/src/decoder/plugins/FaadDecoderPlugin.cxx @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2003-2014 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 "FaadDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "../DecoderBuffer.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <neaacdec.h> + +#include <assert.h> +#include <string.h> +#include <unistd.h> + +static const unsigned adts_sample_rates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +static constexpr Domain faad_decoder_domain("faad_decoder"); + +/** + * 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(DecoderBuffer &buffer) +{ + while (true) { + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(8)); + if (data.IsNull()) + /* failed */ + return 0; + + /* find the 0xff marker */ + const uint8_t *p = (const uint8_t *) + memchr(data.data, 0xff, data.size); + if (p == nullptr) { + /* no marker - discard the buffer */ + buffer.Clear(); + continue; + } + + if (p > data.data) { + /* discard data before 0xff */ + buffer.Consume(p - data.data); + continue; + } + + /* is it a frame? */ + const size_t frame_length = adts_check_frame(data.data); + if (frame_length == 0) { + /* it's just some random 0xff byte; discard it + and continue searching */ + buffer.Consume(1); + continue; + } + + if (buffer.Need(frame_length).IsNull()) { + /* not enough data; discard this frame to + prevent a possible buffer overflow */ + buffer.Clear(); + continue; + } + + /* found a full frame! */ + return frame_length; + } +} + +static SignedSongTime +adts_song_duration(DecoderBuffer &buffer) +{ + const InputStream &is = buffer.GetStream(); + const bool estimate = !is.CheapSeeking(); + if (estimate && !is.KnownSize()) + return SignedSongTime::Negative(); + + unsigned sample_rate = 0; + + /* Read all frames to ensure correct time and bitrate */ + unsigned frames = 0; + for (;; frames++) { + const unsigned frame_length = adts_find_frame(buffer); + if (frame_length == 0) + break; + + if (frames == 0) { + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read()); + assert(!data.IsEmpty()); + assert(frame_length <= data.size); + + sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2]; + if (sample_rate == 0) + break; + } + + buffer.Consume(frame_length); + + if (estimate && frames == 128) { + /* if this is a remote file, don't slurp the + whole file just for checking the song + duration; instead, stop after some time and + extrapolate the song duration from what we + have until now */ + + const auto offset = is.GetOffset() + - buffer.GetAvailable(); + if (offset <= 0) + return SignedSongTime::Negative(); + + const auto file_size = is.GetSize(); + frames = (frames * file_size) / offset; + break; + } + } + + if (sample_rate == 0) + return SignedSongTime::Negative(); + + return SignedSongTime::FromScale<uint64_t>(frames * uint64_t(1024), + sample_rate); +} + +static SignedSongTime +faad_song_duration(DecoderBuffer &buffer, InputStream &is) +{ + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5)); + if (data.IsNull()) + return SignedSongTime::Negative(); + + size_t tagsize = 0; + if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) { + /* skip the ID3 tag */ + + tagsize = (data.data[6] << 21) | (data.data[7] << 14) | + (data.data[8] << 7) | (data.data[9] << 0); + + tagsize += 10; + + if (!buffer.Skip(tagsize)) + return SignedSongTime::Negative(); + + data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5)); + if (data.IsNull()) + return SignedSongTime::Negative(); + } + + if (data.size >= 8 && adts_check_frame(data.data) > 0) { + /* obtain the duration from the ADTS header */ + + if (!is.IsSeekable()) + return SignedSongTime::Negative(); + + auto song_length = adts_song_duration(buffer); + + is.LockSeek(tagsize, IgnoreError()); + + buffer.Clear(); + + return song_length; + } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) { + /* obtain the duration from the ADIF header */ + + if (!is.KnownSize()) + return SignedSongTime::Negative(); + + size_t skip_size = (data.data[4] & 0x80) ? 9 : 0; + + if (8 + skip_size > data.size) + /* not enough data yet; skip parsing this + header */ + return SignedSongTime::Negative(); + + unsigned bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) | + (data.data[5 + skip_size] << 11) | + (data.data[6 + skip_size] << 3) | + (data.data[7 + skip_size] & 0xE0); + + const auto size = is.GetSize(); + if (bit_rate == 0) + return SignedSongTime::Negative(); + + return SongTime::FromScale(size, bit_rate / 8); + } else + return SignedSongTime::Negative(); +} + +static NeAACDecHandle +faad_decoder_new() +{ + const NeAACDecHandle decoder = NeAACDecOpen(); + + NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + config->downMatrix = 1; + config->dontUpSampleImplicitSBR = 0; + NeAACDecSetConfiguration(decoder, config); + + return decoder; +} + +/** + * Wrapper for NeAACDecInit() which works around some API + * inconsistencies in libfaad. + */ +static bool +faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer &buffer, + AudioFormat &audio_format, Error &error) +{ + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read()); + if (data.IsEmpty()) { + error.Set(faad_decoder_domain, "Empty file"); + return false; + } + + uint8_t channels; + unsigned long sample_rate; + long nbytes = NeAACDecInit(decoder, + /* deconst hack, libfaad requires this */ + const_cast<unsigned char *>(data.data), + data.size, + &sample_rate, &channels); + if (nbytes < 0) { + error.Set(faad_decoder_domain, "Not an AAC stream"); + return false; + } + + buffer.Consume(nbytes); + + return audio_format_init_checked(audio_format, sample_rate, + SampleFormat::S16, channels, error); +} + +/** + * Wrapper for NeAACDecDecode() which works around some API + * inconsistencies in libfaad. + */ +static const void * +faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer &buffer, + NeAACDecFrameInfo *frame_info) +{ + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read()); + if (data.IsEmpty()) + return nullptr; + + return NeAACDecDecode(decoder, frame_info, + /* deconst hack, libfaad requires this */ + const_cast<uint8_t *>(data.data), + data.size); +} + +/** + * Determine a song file's total playing time. + * + * The first return value specifies whether the file was recognized. + * The second return value is the duration. + */ +static std::pair<bool, SignedSongTime> +faad_get_file_time(InputStream &is) +{ + DecoderBuffer buffer(nullptr, is, + FAAD_MIN_STREAMSIZE * MAX_CHANNELS); + auto duration = faad_song_duration(buffer, is); + bool recognized = !duration.IsNegative(); + + if (!recognized) { + NeAACDecHandle decoder = faad_decoder_new(); + + buffer.Fill(); + + AudioFormat audio_format; + if (faad_decoder_init(decoder, buffer, audio_format, + IgnoreError())) + recognized = true; + + NeAACDecClose(decoder); + } + + return std::make_pair(recognized, duration); +} + +static void +faad_stream_decode(Decoder &mpd_decoder, InputStream &is, + DecoderBuffer &buffer, const NeAACDecHandle decoder) +{ + const auto total_time = faad_song_duration(buffer, is); + + if (adts_find_frame(buffer) == 0) + return; + + /* initialize it */ + + Error error; + AudioFormat audio_format; + if (!faad_decoder_init(decoder, buffer, audio_format, error)) { + LogError(error); + return; + } + + /* initialize the MPD core */ + + decoder_initialized(mpd_decoder, audio_format, false, total_time); + + /* the decoder loop */ + + DecoderCommand cmd; + unsigned bit_rate = 0; + do { + /* find the next frame */ + + const size_t frame_size = adts_find_frame(buffer); + if (frame_size == 0) + /* end of file */ + break; + + /* decode it */ + + NeAACDecFrameInfo frame_info; + const void *const decoded = + faad_decoder_decode(decoder, buffer, &frame_info); + + if (frame_info.error > 0) { + FormatWarning(faad_decoder_domain, + "error decoding AAC stream: %s", + NeAACDecGetErrorMessage(frame_info.error)); + break; + } + + if (frame_info.channels != audio_format.channels) { + FormatDefault(faad_decoder_domain, + "channel count changed from %u to %u", + audio_format.channels, frame_info.channels); + break; + } + + if (frame_info.samplerate != audio_format.sample_rate) { + FormatDefault(faad_decoder_domain, + "sample rate changed from %u to %lu", + audio_format.sample_rate, + (unsigned long)frame_info.samplerate); + break; + } + + buffer.Consume(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 != DecoderCommand::STOP); +} + +static void +faad_stream_decode(Decoder &mpd_decoder, InputStream &is) +{ + DecoderBuffer buffer(&mpd_decoder, is, + FAAD_MIN_STREAMSIZE * MAX_CHANNELS); + + /* create the libfaad decoder */ + + const NeAACDecHandle decoder = faad_decoder_new(); + + faad_stream_decode(mpd_decoder, is, buffer, decoder); + + /* cleanup */ + + NeAACDecClose(decoder); +} + +static bool +faad_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + auto result = faad_get_file_time(is); + if (!result.first) + return false; + + if (!result.second.IsNegative()) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime(result.second)); + return true; +} + +static const char *const faad_suffixes[] = { "aac", nullptr }; +static const char *const faad_mime_types[] = { + "audio/aac", "audio/aacp", nullptr +}; + +const DecoderPlugin faad_decoder_plugin = { + "faad", + nullptr, + nullptr, + faad_stream_decode, + nullptr, + nullptr, + faad_scan_stream, + nullptr, + faad_suffixes, + faad_mime_types, +}; diff --git a/src/decoder/plugins/FaadDecoderPlugin.hxx b/src/decoder/plugins/FaadDecoderPlugin.hxx new file mode 100644 index 000000000..968433e9b --- /dev/null +++ b/src/decoder/plugins/FaadDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_FAAD_DECODER_PLUGIN_HXX +#define MPD_FAAD_DECODER_PLUGIN_HXX + +extern const struct DecoderPlugin faad_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx new file mode 100644 index 000000000..466caa3d1 --- /dev/null +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -0,0 +1,770 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegDecoderPlugin.hxx" +#include "lib/ffmpeg/Domain.hxx" +#include "../DecoderAPI.hxx" +#include "FfmpegMetaData.hxx" +#include "tag/TagHandler.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "LogV.hxx" + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavformat/avio.h> +#include <libavutil/avutil.h> +#include <libavutil/log.h> +#include <libavutil/mathematics.h> + +#if LIBAVUTIL_VERSION_MAJOR >= 53 +#include <libavutil/frame.h> +#endif +} + +#include <assert.h> +#include <string.h> + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +static LogLevel +import_ffmpeg_level(int level) +{ + if (level <= AV_LOG_FATAL) + return LogLevel::ERROR; + + if (level <= AV_LOG_WARNING) + return LogLevel::WARNING; + + if (level <= AV_LOG_INFO) + return LogLevel::INFO; + + return LogLevel::DEBUG; +} + +static void +mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level, + const char *fmt, va_list vl) +{ + const AVClass * cls = nullptr; + + if (ptr != nullptr) + cls = *(const AVClass *const*)ptr; + + if (cls != nullptr) { + char domain[64]; + snprintf(domain, sizeof(domain), "%s/%s", + ffmpeg_domain.GetName(), cls->item_name(ptr)); + const Domain d(domain); + LogFormatV(d, import_ffmpeg_level(level), fmt, vl); + } +} + +struct AvioStream { + Decoder *const decoder; + InputStream &input; + + AVIOContext *io; + + unsigned char buffer[8192]; + + AvioStream(Decoder *_decoder, InputStream &_input) + :decoder(_decoder), input(_input), io(nullptr) {} + + ~AvioStream() { + if (io != nullptr) + av_free(io); + } + + bool Open(); +}; + +static int +mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) +{ + AvioStream *stream = (AvioStream *)opaque; + + return decoder_read(stream->decoder, stream->input, + (void *)buf, size); +} + +static int64_t +mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) +{ + AvioStream *stream = (AvioStream *)opaque; + + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + pos += stream->input.GetOffset(); + break; + + case SEEK_END: + if (!stream->input.KnownSize()) + return -1; + + pos += stream->input.GetSize(); + break; + + case AVSEEK_SIZE: + if (!stream->input.KnownSize()) + return -1; + + return stream->input.GetSize(); + + default: + return -1; + } + + if (!stream->input.LockSeek(pos, IgnoreError())) + return -1; + + return stream->input.GetOffset(); +} + +bool +AvioStream::Open() +{ + io = avio_alloc_context(buffer, sizeof(buffer), + false, this, + mpd_ffmpeg_stream_read, nullptr, + input.IsSeekable() + ? mpd_ffmpeg_stream_seek : nullptr); + return io != nullptr; +} + +/** + * API compatibility wrapper for av_open_input_stream() and + * avformat_open_input(). + */ +static int +mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, + AVIOContext *pb, + const char *filename, + AVInputFormat *fmt) +{ + AVFormatContext *context = avformat_alloc_context(); + if (context == nullptr) + return AVERROR(ENOMEM); + + context->pb = pb; + *ic_ptr = context; + return avformat_open_input(ic_ptr, filename, fmt, nullptr); +} + +static bool +ffmpeg_init(gcc_unused const config_param ¶m) +{ + av_log_set_callback(mpd_ffmpeg_log_callback); + + av_register_all(); + 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 == + AVMEDIA_TYPE_AUDIO) + return i; + + return -1; +} + +gcc_const +static double +time_from_ffmpeg(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) + / (double)1024; +} + +template<typename Ratio> +static constexpr AVRational +RatioToAVRational() +{ + return { Ratio::num, Ratio::den }; +} + +gcc_const +static int64_t +time_to_ffmpeg(SongTime t, const AVRational time_base) +{ + return av_rescale_q(t.count(), + RatioToAVRational<SongTime::period>(), + time_base); +} + +/** + * Replace #AV_NOPTS_VALUE with the given fallback. + */ +static constexpr int64_t +timestamp_fallback(int64_t t, int64_t fallback) +{ + return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) + ? t + : fallback; +} + +/** + * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with + * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply + * assume that the stream's start time is zero, which appears to be + * the best way out of that situation. + */ +static int64_t +start_time_fallback(const AVStream &stream) +{ + return timestamp_fallback(stream.start_time, 0); +} + +static void +copy_interleave_frame2(uint8_t *dest, uint8_t **src, + unsigned nframes, unsigned nchannels, + unsigned sample_size) +{ + for (unsigned frame = 0; frame < nframes; ++frame) { + for (unsigned channel = 0; channel < nchannels; ++channel) { + memcpy(dest, src[channel] + frame * sample_size, + sample_size); + dest += sample_size; + } + } +} + +/** + * Copy PCM data from a AVFrame to an interleaved buffer. + */ +static int +copy_interleave_frame(const AVCodecContext *codec_context, + const AVFrame *frame, + uint8_t **output_buffer, + uint8_t **global_buffer, int *global_buffer_size) +{ + int plane_size; + const int data_size = + av_samples_get_buffer_size(&plane_size, + codec_context->channels, + frame->nb_samples, + codec_context->sample_fmt, 1); + if (data_size <= 0) + return data_size; + + if (av_sample_fmt_is_planar(codec_context->sample_fmt) && + codec_context->channels > 1) { + if(*global_buffer_size < data_size) { + av_freep(global_buffer); + + *global_buffer = (uint8_t*)av_malloc(data_size); + + if (!*global_buffer) + /* Not enough memory - shouldn't happen */ + return AVERROR(ENOMEM); + *global_buffer_size = data_size; + } + *output_buffer = *global_buffer; + copy_interleave_frame2(*output_buffer, frame->extended_data, + frame->nb_samples, + codec_context->channels, + av_get_bytes_per_sample(codec_context->sample_fmt)); + } else { + *output_buffer = frame->extended_data[0]; + } + + return data_size; +} + +static DecoderCommand +ffmpeg_send_packet(Decoder &decoder, InputStream &is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVStream *stream, + AVFrame *frame, + uint8_t **buffer, int *buffer_size) +{ + if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + time_from_ffmpeg(packet->pts - start_time_fallback(*stream), + stream->time_base)); + + AVPacket packet2 = *packet; + + uint8_t *output_buffer; + + DecoderCommand cmd = DecoderCommand::NONE; + while (packet2.size > 0 && cmd == DecoderCommand::NONE) { + int audio_size = 0; + int got_frame = 0; + int len = avcodec_decode_audio4(codec_context, + frame, &got_frame, + &packet2); + if (len >= 0 && got_frame) { + audio_size = copy_interleave_frame(codec_context, + frame, + &output_buffer, + buffer, buffer_size); + if (audio_size < 0) + len = audio_size; + } + + if (len < 0) { + /* if error, we skip the frame */ + LogDefault(ffmpeg_domain, + "decoding failed, frame skipped"); + break; + } + + packet2.data += len; + packet2.size -= len; + + if (audio_size <= 0) + continue; + + cmd = decoder_data(decoder, is, + output_buffer, audio_size, + codec_context->bit_rate / 1000); + } + return cmd; +} + +gcc_const +static SampleFormat +ffmpeg_sample_format(enum AVSampleFormat sample_fmt) +{ + switch (sample_fmt) { + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + return SampleFormat::S16; + + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + return SampleFormat::S32; + + case AV_SAMPLE_FMT_FLTP: + return SampleFormat::FLOAT; + + default: + break; + } + + char buffer[64]; + const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), + sample_fmt); + if (name != nullptr) + FormatError(ffmpeg_domain, + "Unsupported libavcodec SampleFormat value: %s (%d)", + name, sample_fmt); + else + FormatError(ffmpeg_domain, + "Unsupported libavcodec SampleFormat value: %d", + sample_fmt); + return SampleFormat::UNDEFINED; +} + +static AVInputFormat * +ffmpeg_probe(Decoder *decoder, InputStream &is) +{ + enum { + BUFFER_SIZE = 16384, + PADDING = 16, + }; + + unsigned char buffer[BUFFER_SIZE]; + size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); + if (nbytes <= PADDING || !is.LockRewind(IgnoreError())) + return nullptr; + + /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes + beyond the declared buffer limit, which makes valgrind + angry; this workaround removes some padding from the buffer + size */ + nbytes -= PADDING; + + AVProbeData avpd; + + /* new versions of ffmpeg may add new attributes, and leaving + them uninitialized may crash; hopefully, zero-initializing + everything we don't know is ok */ + memset(&avpd, 0, sizeof(avpd)); + + avpd.buf = buffer; + avpd.buf_size = nbytes; + avpd.filename = is.GetURI(); + +#ifdef AVPROBE_SCORE_MIME + /* this attribute was added in libav/ffmpeg version 11, but + unfortunately it's "uint8_t" instead of "char", and it's + not "const" - wtf? */ + avpd.mime_type = (uint8_t *)const_cast<char *>(is.GetMimeType()); +#endif + + return av_probe_input_format(&avpd, true); +} + +static void +ffmpeg_decode(Decoder &decoder, InputStream &input) +{ + AVInputFormat *input_format = ffmpeg_probe(&decoder, input); + if (input_format == nullptr) + return; + + FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", + input_format->name, input_format->long_name); + + AvioStream stream(&decoder, input); + if (!stream.Open()) { + LogError(ffmpeg_domain, "Failed to open stream"); + return; + } + + //ffmpeg works with ours "fileops" helper + AVFormatContext *format_context = nullptr; + if (mpd_ffmpeg_open_input(&format_context, stream.io, + input.GetURI(), + input_format) != 0) { + LogError(ffmpeg_domain, "Open failed"); + return; + } + + const int find_result = + avformat_find_stream_info(format_context, nullptr); + if (find_result < 0) { + LogError(ffmpeg_domain, "Couldn't find stream info"); + avformat_close_input(&format_context); + return; + } + + int audio_stream = ffmpeg_find_audio_stream(format_context); + if (audio_stream == -1) { + LogError(ffmpeg_domain, "No audio stream inside"); + avformat_close_input(&format_context); + return; + } + + AVStream *av_stream = format_context->streams[audio_stream]; + + AVCodecContext *codec_context = av_stream->codec; + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0) + const AVCodecDescriptor *codec_descriptor = + avcodec_descriptor_get(codec_context->codec_id); + if (codec_descriptor != nullptr) + FormatDebug(ffmpeg_domain, "codec '%s'", + codec_descriptor->name); +#else + if (codec_context->codec_name[0] != 0) + FormatDebug(ffmpeg_domain, "codec '%s'", + codec_context->codec_name); +#endif + + AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); + + if (!codec) { + LogError(ffmpeg_domain, "Unsupported audio codec"); + avformat_close_input(&format_context); + return; + } + + const SampleFormat sample_format = + ffmpeg_sample_format(codec_context->sample_fmt); + if (sample_format == SampleFormat::UNDEFINED) { + // (error message already done by ffmpeg_sample_format()) + avformat_close_input(&format_context); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + codec_context->sample_rate, + sample_format, + codec_context->channels, error)) { + LogError(error); + avformat_close_input(&format_context); + return; + } + + /* the audio format must be read from AVCodecContext by now, + because avcodec_open() has been demonstrated to fill bogus + values into AVCodecContext.channels - a change that will be + reverted later by avcodec_decode_audio3() */ + + const int open_result = avcodec_open2(codec_context, codec, nullptr); + if (open_result < 0) { + LogError(ffmpeg_domain, "Could not open codec"); + avformat_close_input(&format_context); + return; + } + + const SignedSongTime total_time = + format_context->duration != (int64_t)AV_NOPTS_VALUE + ? SignedSongTime::FromScale<uint64_t>(format_context->duration, + AV_TIME_BASE) + : SignedSongTime::Negative(); + + decoder_initialized(decoder, audio_format, + input.IsSeekable(), total_time); + +#if LIBAVUTIL_VERSION_MAJOR >= 53 + AVFrame *frame = av_frame_alloc(); +#else + AVFrame *frame = avcodec_alloc_frame(); +#endif + if (!frame) { + LogError(ffmpeg_domain, "Could not allocate frame"); + avformat_close_input(&format_context); + return; + } + + uint8_t *interleaved_buffer = nullptr; + int interleaved_buffer_size = 0; + + DecoderCommand cmd; + do { + AVPacket packet; + if (av_read_frame(format_context, &packet) < 0) + /* end of file */ + break; + + if (packet.stream_index == audio_stream) + cmd = ffmpeg_send_packet(decoder, input, + &packet, codec_context, + av_stream, + frame, + &interleaved_buffer, &interleaved_buffer_size); + else + cmd = decoder_get_command(decoder); + + av_free_packet(&packet); + + if (cmd == DecoderCommand::SEEK) { + int64_t where = + time_to_ffmpeg(decoder_seek_time(decoder), + av_stream->time_base) + + start_time_fallback(*av_stream); + + if (av_seek_frame(format_context, audio_stream, where, + AVSEEK_FLAG_ANY) < 0) + decoder_seek_error(decoder); + else { + avcodec_flush_buffers(codec_context); + decoder_command_finished(decoder); + } + } + } while (cmd != DecoderCommand::STOP); + +#if LIBAVUTIL_VERSION_MAJOR >= 53 + av_frame_free(&frame); +#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) + avcodec_free_frame(&frame); +#else + av_freep(&frame); +#endif + av_freep(&interleaved_buffer); + + avcodec_close(codec_context); + avformat_close_input(&format_context); +} + +//no tag reading in ffmpeg, check if playable +static bool +ffmpeg_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + AVInputFormat *input_format = ffmpeg_probe(nullptr, is); + if (input_format == nullptr) + return false; + + AvioStream stream(nullptr, is); + if (!stream.Open()) + return false; + + AVFormatContext *f = nullptr; + if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(), + input_format) != 0) + return false; + + const int find_result = + avformat_find_stream_info(f, nullptr); + if (find_result < 0) { + avformat_close_input(&f); + return false; + } + + if (f->duration != (int64_t)AV_NOPTS_VALUE) { + const auto duration = + SongTime::FromScale<uint64_t>(f->duration, + AV_TIME_BASE); + tag_handler_invoke_duration(handler, handler_ctx, duration); + } + + ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); + int idx = ffmpeg_find_audio_stream(f); + if (idx >= 0) + ffmpeg_scan_dictionary(f->streams[idx]->metadata, + handler, handler_ctx); + + avformat_close_input(&f); + return true; +} + +/** + * 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", "m4b", "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", "opus", "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", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", + "wve", + nullptr +}; + +static const char *const ffmpeg_mime_types[] = { + "application/flv", + "application/m4a", + "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/aacp", + "audio/ac3", + "audio/aiff" + "audio/amr", + "audio/basic", + "audio/flac", + "audio/m4a", + "audio/mp4", + "audio/mpeg", + "audio/musepack", + "audio/ogg", + "audio/opus", + "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-matroska", + "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-voc", + "audio/x-wav", + "audio/x-wma", + "audio/x-wv", + "video/anim", + "video/quicktime", + "video/msvideo", + "video/ogg", + "video/theora", + "video/webm", + "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", + + /* special value for the "ffmpeg" input plugin: all streams by + the "ffmpeg" input plugin shall be decoded by this + plugin */ + "audio/x-mpd-ffmpeg", + + nullptr +}; + +const struct DecoderPlugin ffmpeg_decoder_plugin = { + "ffmpeg", + ffmpeg_init, + nullptr, + ffmpeg_decode, + nullptr, + nullptr, + ffmpeg_scan_stream, + nullptr, + ffmpeg_suffixes, + ffmpeg_mime_types +}; diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.hxx b/src/decoder/plugins/FfmpegDecoderPlugin.hxx new file mode 100644 index 000000000..0a3e78e4b --- /dev/null +++ b/src/decoder/plugins/FfmpegDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_FFMPEG_HXX +#define MPD_DECODER_FFMPEG_HXX + +extern const struct DecoderPlugin ffmpeg_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx new file mode 100644 index 000000000..a39466945 --- /dev/null +++ b/src/decoder/plugins/FfmpegMetaData.cxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegMetaData.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagHandler.hxx" + +static const struct tag_table ffmpeg_tags[] = { + { "year", TAG_DATE }, + { "author-sort", TAG_ARTIST_SORT }, + { "album_artist", TAG_ALBUM_ARTIST }, + { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, + + /* sentinel */ + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; + +static void +ffmpeg_copy_metadata(TagType type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *mt = nullptr; + + while ((mt = av_dict_get(m, name, mt, 0)) != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + type, mt->value); +} + +static void +ffmpeg_scan_pairs(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *i = nullptr; + + while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr) + tag_handler_invoke_pair(handler, handler_ctx, + i->key, i->value); +} + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], + handler, handler_ctx); + + for (const struct tag_table *i = ffmpeg_tags; + i->name != nullptr; ++i) + ffmpeg_copy_metadata(i->type, dict, i->name, + handler, handler_ctx); + + if (handler->pair != nullptr) + ffmpeg_scan_pairs(dict, handler, handler_ctx); +} diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx new file mode 100644 index 000000000..5eb41db68 --- /dev/null +++ b/src/decoder/plugins/FfmpegMetaData.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 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_FFMPEG_METADATA_HXX +#define MPD_FFMPEG_METADATA_HXX + +extern "C" { +#include <libavutil/dict.h> +} + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +struct tag_handler; + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/plugins/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx new file mode 100644 index 000000000..e86f85569 --- /dev/null +++ b/src/decoder/plugins/FlacCommon.cxx @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2014 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 + */ + +#include "config.h" +#include "FlacCommon.hxx" +#include "FlacMetadata.hxx" +#include "FlacPcm.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +flac_data::flac_data(Decoder &_decoder, + InputStream &_input_stream) + :FlacInput(_input_stream, &_decoder), + initialized(false), unsupported(false), + total_frames(0), first_frame(0), next_frame(0), position(0), + decoder(_decoder), input_stream(_input_stream) +{ +} + +static SampleFormat +flac_sample_format(unsigned bits_per_sample) +{ + switch (bits_per_sample) { + case 8: + return SampleFormat::S8; + + case 16: + return SampleFormat::S16; + + case 24: + return SampleFormat::S24_P32; + + case 32: + return SampleFormat::S32; + + default: + return SampleFormat::UNDEFINED; + } +} + +static void +flac_got_stream_info(struct flac_data *data, + const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + if (data->initialized || data->unsupported) + return; + + Error error; + if (!audio_format_init_checked(data->audio_format, + stream_info->sample_rate, + flac_sample_format(stream_info->bits_per_sample), + stream_info->channels, error)) { + LogError(error); + data->unsupported = true; + return; + } + + data->frame_size = data->audio_format.GetFrameSize(); + + if (data->total_frames == 0) + data->total_frames = stream_info->total_samples; + + data->initialized = true; +} + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + struct flac_data *data) +{ + if (data->unsupported) + return; + + ReplayGainInfo rgi; + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + flac_got_stream_info(data, &block->data.stream_info); + break; + + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + if (flac_parse_replay_gain(rgi, block->data.vorbis_comment)) + decoder_replay_gain(data->decoder, &rgi); + + decoder_mixramp(data->decoder, + flac_parse_mixramp(block->data.vorbis_comment)); + + data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment); + break; + + default: + break; + } +} + +/** + * This function attempts to call decoder_initialized() in case there + * was no STREAMINFO block. This is allowed for nonseekable streams, + * where the server sends us only a part of the file, without + * providing the STREAMINFO block from the beginning of the file + * (e.g. when seeking with SqueezeBox Server). + */ +static bool +flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) +{ + if (data->unsupported) + return false; + + Error error; + if (!audio_format_init_checked(data->audio_format, + header->sample_rate, + flac_sample_format(header->bits_per_sample), + header->channels, error)) { + LogError(error); + data->unsupported = true; + return false; + } + + data->frame_size = data->audio_format.GetFrameSize(); + + const auto duration = SongTime::FromScale<uint64_t>(data->total_frames, + data->audio_format.sample_rate); + + decoder_initialized(data->decoder, data->audio_format, + data->input_stream.IsSeekable(), + duration); + + data->initialized = true; + + return true; +} + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes) +{ + void *buffer; + unsigned bit_rate; + + if (!data->initialized && !flac_got_first_frame(data, &frame->header)) + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + size_t buffer_size = frame->header.blocksize * data->frame_size; + buffer = data->buffer.Get(buffer_size); + + flac_convert(buffer, frame->header.channels, + data->audio_format.format, buf, + 0, frame->header.blocksize); + + if (nbytes > 0) + bit_rate = nbytes * 8 * frame->header.sample_rate / + (1000 * frame->header.blocksize); + else + bit_rate = 0; + + auto cmd = decoder_data(data->decoder, data->input_stream, + buffer, buffer_size, + bit_rate); + data->next_frame += frame->header.blocksize; + switch (cmd) { + case DecoderCommand::NONE: + case DecoderCommand::START: + break; + + case DecoderCommand::STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + case DecoderCommand::SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} diff --git a/src/decoder/plugins/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx new file mode 100644 index 000000000..34ce0a3fc --- /dev/null +++ b/src/decoder/plugins/FlacCommon.hxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2014 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_COMMON_HXX +#define MPD_FLAC_COMMON_HXX + +#include "FlacInput.hxx" +#include "../DecoderAPI.hxx" +#include "pcm/PcmBuffer.hxx" + +#include <FLAC/stream_decoder.h> + +struct flac_data : public FlacInput { + PcmBuffer buffer; + + /** + * The size of one frame in the output buffer. + */ + unsigned frame_size; + + /** + * Has decoder_initialized() been called yet? + */ + bool initialized; + + /** + * Does the FLAC file contain an unsupported audio format? + */ + bool unsupported; + + /** + * The validated audio format of the FLAC file. This + * attribute is defined if "initialized" is true. + */ + AudioFormat audio_format; + + /** + * The total number of frames in this song. The decoder + * plugin may initialize this attribute to override the value + * provided by libFLAC (e.g. for sub songs from a CUE sheet). + */ + FLAC__uint64 total_frames; + + /** + * The number of the first frame in this song. This is only + * non-zero if playing sub songs from a CUE sheet. + */ + FLAC__uint64 first_frame; + + /** + * The number of the next frame which is going to be decoded. + */ + FLAC__uint64 next_frame; + + FLAC__uint64 position; + + Decoder &decoder; + InputStream &input_stream; + + Tag tag; + + flac_data(Decoder &decoder, InputStream &input_stream); +}; + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + struct flac_data *data); + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes); + +#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/plugins/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx new file mode 100644 index 000000000..eea813401 --- /dev/null +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2003-2014 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 "FlacDecoderPlugin.h" +#include "FlacDomain.hxx" +#include "FlacCommon.hxx" +#include "FlacMetadata.hxx" +#include "OggCodec.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + 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: + case FLAC__STREAM_DECODER_SEEK_ERROR: + case FLAC__STREAM_DECODER_ABORTED: + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + case FLAC__STREAM_DECODER_UNINITIALIZED: + break; + } + + LogError(flac_domain, FLAC__StreamDecoderStateString[state]); +} + +static void flacMetadata(gcc_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 bool +flac_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.Read(path_fs.c_str())) { + FormatDebug(flac_domain, + "Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +flac_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.Read(is)) { + FormatDebug(flac_domain, + "Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) +{ + FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); + if (sd == nullptr) { + LogError(flac_domain, + "FLAC__stream_decoder_new() failed"); + return nullptr; + } + + if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + LogDebug(flac_domain, + "FLAC__stream_decoder_set_metadata_respond() has failed"); + + return sd; +} + +static bool +flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, + FLAC__uint64 duration) +{ + data->total_frames = duration; + + if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { + LogWarning(flac_domain, "problem reading metadata"); + return false; + } + + if (data->initialized) { + /* done */ + + const auto duration2 = + SongTime::FromScale<uint64_t>(data->total_frames, + data->audio_format.sample_rate); + + decoder_initialized(data->decoder, data->audio_format, + data->input_stream.IsSeekable(), + duration2); + return true; + } + + if (data->input_stream.IsSeekable()) + /* allow the workaround below only for nonseekable + streams*/ + return false; + + /* no stream_info packet found; try to initialize the decoder + from the first frame header */ + FLAC__stream_decoder_process_single(sd); + return data->initialized; +} + +static void +flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, + FLAC__uint64 t_start, FLAC__uint64 t_end) +{ + Decoder &decoder = data->decoder; + + data->first_frame = t_start; + + while (true) { + DecoderCommand cmd; + if (!data->tag.IsEmpty()) { + cmd = decoder_tag(data->decoder, data->input_stream, + std::move(data->tag)); + data->tag.Clear(); + } else + cmd = decoder_get_command(decoder); + + if (cmd == DecoderCommand::SEEK) { + FLAC__uint64 seek_sample = t_start + + decoder_seek_where_frame(decoder); + 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 == DecoderCommand::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) && + decoder_get_command(decoder) == DecoderCommand::NONE) { + /* a failure that was not triggered by a + decoder command */ + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + break; + } + } +} + +static FLAC__StreamDecoderInitStatus +stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_ogg_stream(flac_dec, + FlacInput::Read, + FlacInput::Seek, + FlacInput::Tell, + FlacInput::Length, + FlacInput::Eof, + flac_write_cb, + flacMetadata, + FlacInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_stream(flac_dec, + FlacInput::Read, + FlacInput::Seek, + FlacInput::Tell, + FlacInput::Length, + FlacInput::Eof, + flac_write_cb, + flacMetadata, + FlacInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) +{ + return is_ogg + ? stream_init_oggflac(flac_dec, data) + : stream_init_flac(flac_dec, data); +} + +static void +flac_decode_internal(Decoder &decoder, + InputStream &input_stream, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + + flac_dec = flac_decoder_new(); + if (flac_dec == nullptr) + return; + + struct flac_data data(decoder, input_stream); + + FLAC__StreamDecoderInitStatus status = + stream_init(flac_dec, &data, is_ogg); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + FLAC__stream_decoder_delete(flac_dec); + LogWarning(flac_domain, + FLAC__StreamDecoderInitStatusString[status]); + return; + } + + if (!flac_decoder_initialize(&data, flac_dec, 0)) { + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + flac_decoder_loop(&data, flac_dec, 0, 0); + + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); +} + +static void +flac_decode(Decoder &decoder, InputStream &input_stream) +{ + flac_decode_internal(decoder, input_stream, false); +} + +static bool +oggflac_init(gcc_unused const config_param ¶m) +{ + return !!FLAC_API_SUPPORTS_OGG_FLAC; +} + +static bool +oggflac_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.ReadOgg(path_fs.c_str())) { + FormatDebug(flac_domain, + "Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +oggflac_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.ReadOgg(is)) { + FormatDebug(flac_domain, + "Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static void +oggflac_decode(Decoder &decoder, InputStream &input_stream) +{ + if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_FLAC) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream.LockRewind(IgnoreError()); + + flac_decode_internal(decoder, input_stream, true); +} + +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", + nullptr +}; + +const struct DecoderPlugin oggflac_decoder_plugin = { + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + oggflac_scan_stream, + nullptr, + oggflac_suffixes, + oggflac_mime_types, +}; + +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", + nullptr +}; + +const struct DecoderPlugin flac_decoder_plugin = { + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + flac_scan_stream, + nullptr, + flac_suffixes, + flac_mime_types, +}; diff --git a/src/decoder/plugins/FlacDecoderPlugin.h b/src/decoder/plugins/FlacDecoderPlugin.h new file mode 100644 index 000000000..fcdecf869 --- /dev/null +++ b/src/decoder/plugins/FlacDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2014 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 DecoderPlugin flac_decoder_plugin; +extern const struct DecoderPlugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FlacDomain.cxx b/src/decoder/plugins/FlacDomain.cxx new file mode 100644 index 000000000..fc5cc5498 --- /dev/null +++ b/src/decoder/plugins/FlacDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 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 "FlacDomain.hxx" +#include "util/Domain.hxx" + +const Domain flac_domain("flac"); diff --git a/src/decoder/plugins/FlacDomain.hxx b/src/decoder/plugins/FlacDomain.hxx new file mode 100644 index 000000000..a06c6c6b4 --- /dev/null +++ b/src/decoder/plugins/FlacDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 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_FLAC_DOMAIN_HXX +#define MPD_FLAC_DOMAIN_HXX + +#include "check.h" + +extern const class Domain flac_domain; + +#endif diff --git a/src/decoder/plugins/FlacIOHandle.cxx b/src/decoder/plugins/FlacIOHandle.cxx new file mode 100644 index 000000000..0dd895798 --- /dev/null +++ b/src/decoder/plugins/FlacIOHandle.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2014 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 "FlacIOHandle.hxx" +#include "util/Error.hxx" +#include "Compiler.h" + +#include <errno.h> +#include <stdio.h> + +static size_t +FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + uint8_t *const p0 = (uint8_t *)ptr, *p = p0, + *const end = p0 + size * nmemb; + + /* libFLAC is very picky about short reads, and expects the IO + callback to fill the whole buffer (undocumented!) */ + + Error error; + while (p < end) { + size_t nbytes = is->LockRead(p, end - p, error); + if (nbytes == 0) { + if (!error.IsDefined()) + /* end of file */ + break; + + if (error.IsDomain(errno_domain)) + errno = error.GetCode(); + else + /* just some random non-zero + errno value */ + errno = EINVAL; + return 0; + } + + p += nbytes; + } + + /* libFLAC expects a clean errno after returning from the IO + callbacks (undocumented!) */ + errno = 0; + return (p - p0) / size; +} + +static int +FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 _offset, int whence) +{ + InputStream *is = (InputStream *)handle; + + offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is->GetOffset(); + break; + + case SEEK_END: + if (!is->KnownSize()) + return -1; + + offset += is->GetSize(); + break; + + default: + return -1; + } + + return is->LockSeek(offset, IgnoreError()) ? 0 : -1; +} + +static FLAC__int64 +FlacIOTell(FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + return is->GetOffset(); +} + +static int +FlacIOEof(FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + return is->LockIsEOF(); +} + +static int +FlacIOClose(gcc_unused FLAC__IOHandle handle) +{ + /* no-op because the libFLAC caller is repsonsible for closing + the #InputStream */ + + return 0; +} + +const FLAC__IOCallbacks flac_io_callbacks = { + FlacIORead, + nullptr, + nullptr, + nullptr, + FlacIOEof, + FlacIOClose, +}; + +const FLAC__IOCallbacks flac_io_callbacks_seekable = { + FlacIORead, + nullptr, + FlacIOSeek, + FlacIOTell, + FlacIOEof, + FlacIOClose, +}; diff --git a/src/decoder/plugins/FlacIOHandle.hxx b/src/decoder/plugins/FlacIOHandle.hxx new file mode 100644 index 000000000..90acc66af --- /dev/null +++ b/src/decoder/plugins/FlacIOHandle.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 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_FLAC_IO_HANDLE_HXX +#define MPD_FLAC_IO_HANDLE_HXX + +#include "Compiler.h" +#include "input/InputStream.hxx" + +#include <FLAC/callback.h> + +extern const FLAC__IOCallbacks flac_io_callbacks; +extern const FLAC__IOCallbacks flac_io_callbacks_seekable; + +static inline FLAC__IOHandle +ToFlacIOHandle(InputStream &is) +{ + return (FLAC__IOHandle)&is; +} + +static inline const FLAC__IOCallbacks & +GetFlacIOCallbacks(const InputStream &is) +{ + return is.IsSeekable() + ? flac_io_callbacks_seekable + : flac_io_callbacks; +} + +#endif diff --git a/src/decoder/plugins/FlacInput.cxx b/src/decoder/plugins/FlacInput.cxx new file mode 100644 index 000000000..5b4c3104d --- /dev/null +++ b/src/decoder/plugins/FlacInput.cxx @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2014 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 "FlacInput.hxx" +#include "FlacDomain.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Compiler.h" + +FLAC__StreamDecoderReadStatus +FlacInput::Read(FLAC__byte buffer[], size_t *bytes) +{ + size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes); + *bytes = r; + + if (r == 0) { + if (input_stream.LockIsEOF() || + (decoder != nullptr && + decoder_get_command(*decoder) != DecoderCommand::NONE)) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +FLAC__StreamDecoderSeekStatus +FlacInput::Seek(FLAC__uint64 absolute_byte_offset) +{ + if (!input_stream.IsSeekable()) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + ::Error error; + if (!input_stream.LockSeek(absolute_byte_offset, error)) { + LogError(error); + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + } + + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__StreamDecoderTellStatus +FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) +{ + if (!input_stream.IsSeekable()) + return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; + + *absolute_byte_offset = (FLAC__uint64)input_stream.GetOffset(); + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +FLAC__StreamDecoderLengthStatus +FlacInput::Length(FLAC__uint64 *stream_length) +{ + if (!input_stream.KnownSize()) + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + + *stream_length = (FLAC__uint64)input_stream.GetSize(); + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +FLAC__bool +FlacInput::Eof() +{ + return (decoder != nullptr && + decoder_get_command(*decoder) != DecoderCommand::NONE && + decoder_get_command(*decoder) != DecoderCommand::SEEK) || + input_stream.LockIsEOF(); +} + +void +FlacInput::Error(FLAC__StreamDecoderErrorStatus status) +{ + if (decoder == nullptr || + decoder_get_command(*decoder) != DecoderCommand::STOP) + LogWarning(flac_domain, + FLAC__StreamDecoderErrorStatusString[status]); +} + +FLAC__StreamDecoderReadStatus +FlacInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, + void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Read(buffer, bytes); +} + +FLAC__StreamDecoderSeekStatus +FlacInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Seek(absolute_byte_offset); +} + +FLAC__StreamDecoderTellStatus +FlacInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Tell(absolute_byte_offset); +} + +FLAC__StreamDecoderLengthStatus +FlacInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Length(stream_length); +} + +FLAC__bool +FlacInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder, + void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Eof(); +} + +void +FlacInput::Error(gcc_unused const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + i->Error(status); +} + diff --git a/src/decoder/plugins/FlacInput.hxx b/src/decoder/plugins/FlacInput.hxx new file mode 100644 index 000000000..427abccb4 --- /dev/null +++ b/src/decoder/plugins/FlacInput.hxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2014 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_FLAC_INPUT_HXX +#define MPD_FLAC_INPUT_HXX + +#include <FLAC/stream_decoder.h> + +struct Decoder; +class InputStream; + +/** + * This class wraps an #InputStream in libFLAC stream decoder + * callbacks. + */ +class FlacInput { + Decoder *const decoder; + + InputStream &input_stream; + +public: + FlacInput(InputStream &_input_stream, + Decoder *_decoder=nullptr) + :decoder(_decoder), input_stream(_input_stream) {} + +protected: + FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); + FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); + FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); + FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); + FLAC__bool Eof(); + void Error(FLAC__StreamDecoderErrorStatus status); + +public: + static FLAC__StreamDecoderReadStatus + Read(const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, void *client_data); + + static FLAC__StreamDecoderSeekStatus + Seek(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderTellStatus + Tell(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderLengthStatus + Length(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data); + + static FLAC__bool + Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); + + static void + Error(const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data); +}; + +#endif diff --git a/src/decoder/plugins/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx new file mode 100644 index 000000000..03e276dce --- /dev/null +++ b/src/decoder/plugins/FlacMetadata.cxx @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2003-2014 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 "FlacMetadata.hxx" +#include "XiphTags.hxx" +#include "MixRampInfo.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/Tag.hxx" +#include "tag/VorbisComment.hxx" +#include "tag/ReplayGain.hxx" +#include "tag/MixRamp.hxx" +#include "ReplayGainInfo.hxx" +#include "util/ASCII.hxx" +#include "util/SplitString.hxx" + +bool +flac_parse_replay_gain(ReplayGainInfo &rgi, + const FLAC__StreamMetadata_VorbisComment &vc) +{ + rgi.Clear(); + + bool found = false; + + const auto *comments = vc.comments; + for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i) + if (ParseReplayGainVorbis(rgi, + (const char *)comments[i].entry)) + found = true; + + return found; +} + +MixRampInfo +flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc) +{ + MixRampInfo mix_ramp; + + const auto *comments = vc.comments; + for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i) + ParseMixRampVorbis(mix_ramp, + (const char *)comments[i].entry); + + return mix_ramp; +} + +/** + * Checks if the specified name matches the entry's name, and if yes, + * returns the comment value; + */ +static const char * +flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name) +{ + return vorbis_comment_value((const char *)entry->entry, name); +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, TagType tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value = flac_comment_value(entry, name); + if (value != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + return true; + } + + return false; +} + +static void +flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != nullptr) { + const char *comment = (const char *)entry->entry; + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) + tag_handler_invoke_pair(handler, handler_ctx, + split.GetFirst(), + split.GetSecond()); + } + + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (flac_copy_comment(entry, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (flac_copy_comment(entry, + tag_item_names[i], (TagType)i, + handler, handler_ctx)) + return; +} + +static void +flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < comment->num_comments; ++i) + flac_scan_comment(&comment->comments[i], + handler, handler_ctx); +} + +gcc_pure +static inline SongTime +flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + assert(stream_info->sample_rate > 0); + + return SongTime::FromScale<uint64_t>(stream_info->total_samples, + stream_info->sample_rate); +} + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const struct tag_handler *handler, void *handler_ctx) +{ + switch (block->type) { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flac_scan_comments(&block->data.vorbis_comment, + handler, handler_ctx); + break; + + case FLAC__METADATA_TYPE_STREAMINFO: + if (block->data.stream_info.sample_rate > 0) + tag_handler_invoke_duration(handler, handler_ctx, + flac_duration(&block->data.stream_info)); + break; + + default: + break; + } +} + +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment) +{ + TagBuilder tag_builder; + flac_scan_comments(comment, &add_tag_handler, &tag_builder); + return tag_builder.Commit(); +} + +void +FlacMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataIterator iterator(*this); + + do { + FLAC__StreamMetadata *block = iterator.GetBlock(); + if (block == nullptr) + break; + + flac_scan_metadata(block, handler, handler_ctx); + } while (iterator.Next()); +} diff --git a/src/decoder/plugins/FlacMetadata.hxx b/src/decoder/plugins/FlacMetadata.hxx new file mode 100644 index 000000000..d791fa34e --- /dev/null +++ b/src/decoder/plugins/FlacMetadata.hxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2014 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_FLAC_METADATA_H +#define MPD_FLAC_METADATA_H + +#include "Compiler.h" +#include "FlacIOHandle.hxx" + +#include <FLAC/metadata.h> + +#include <assert.h> + +struct tag_handler; +class MixRampInfo; + +class FlacMetadataChain { + FLAC__Metadata_Chain *chain; + +public: + FlacMetadataChain():chain(::FLAC__metadata_chain_new()) {} + + ~FlacMetadataChain() { + ::FLAC__metadata_chain_delete(chain); + } + + explicit operator FLAC__Metadata_Chain *() { + return chain; + } + + bool Read(const char *path) { + return ::FLAC__metadata_chain_read(chain, path); + } + + bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_with_callbacks(chain, + handle, + callbacks); + } + + bool Read(InputStream &is) { + return Read(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); + } + + bool ReadOgg(const char *path) { + return ::FLAC__metadata_chain_read_ogg(chain, path); + } + + bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain, + handle, + callbacks); + } + + bool ReadOgg(InputStream &is) { + return ReadOgg(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); + } + + gcc_pure + FLAC__Metadata_ChainStatus GetStatus() const { + return ::FLAC__metadata_chain_status(chain); + } + + gcc_pure + const char *GetStatusString() const { + return FLAC__Metadata_ChainStatusString[GetStatus()]; + } + + void Scan(const tag_handler *handler, void *handler_ctx); +}; + +class FLACMetadataIterator { + FLAC__Metadata_Iterator *iterator; + +public: + FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {} + + FLACMetadataIterator(FlacMetadataChain &chain) + :iterator(::FLAC__metadata_iterator_new()) { + ::FLAC__metadata_iterator_init(iterator, + (FLAC__Metadata_Chain *)chain); + } + + ~FLACMetadataIterator() { + ::FLAC__metadata_iterator_delete(iterator); + } + + bool Next() { + return ::FLAC__metadata_iterator_next(iterator); + } + + gcc_pure + FLAC__StreamMetadata *GetBlock() { + return ::FLAC__metadata_iterator_get_block(iterator); + } +}; + +struct Tag; +struct ReplayGainInfo; + +bool +flac_parse_replay_gain(ReplayGainInfo &rgi, + const FLAC__StreamMetadata_VorbisComment &vc); + +MixRampInfo +flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc); + +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment); + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/plugins/FlacPcm.cxx b/src/decoder/plugins/FlacPcm.cxx new file mode 100644 index 000000000..311500f26 --- /dev/null +++ b/src/decoder/plugins/FlacPcm.cxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2014 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 "FlacPcm.hxx" + +#include <assert.h> + +static void flac_convert_stereo16(int16_t *dest, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + for (; position < end; ++position) { + *dest++ = buf[0][position]; + *dest++ = buf[1][position]; + } +} + +static void +flac_convert_16(int16_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +/** + * Note: this function also handles 24 bit files! + */ +static void +flac_convert_32(int32_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +static void +flac_convert_8(int8_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +void +flac_convert(void *dest, + unsigned int num_channels, SampleFormat sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end) +{ + switch (sample_format) { + case SampleFormat::S16: + if (num_channels == 2) + flac_convert_stereo16((int16_t*)dest, buf, + position, end); + else + flac_convert_16((int16_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::S24_P32: + case SampleFormat::S32: + flac_convert_32((int32_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::S8: + flac_convert_8((int8_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::FLOAT: + case SampleFormat::DSD: + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + } +} diff --git a/src/decoder/plugins/FlacPcm.hxx b/src/decoder/plugins/FlacPcm.hxx new file mode 100644 index 000000000..30c318725 --- /dev/null +++ b/src/decoder/plugins/FlacPcm.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 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_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX + +#include "AudioFormat.hxx" + +#include <FLAC/ordinals.h> + +void +flac_convert(void *dest, + unsigned int num_channels, SampleFormat sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end); + +#endif diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx new file mode 100644 index 000000000..f19ac5bf4 --- /dev/null +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2003-2014 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 "FluidsynthDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <fluidsynth.h> + +static constexpr Domain fluidsynth_domain("fluidsynth"); + +static unsigned sample_rate; +static const char *soundfont_path; + +/** + * Convert a fluidsynth log level to a GLib log level. + */ +static LogLevel +fluidsynth_level_to_mpd(enum fluid_log_level level) +{ + switch (level) { + case FLUID_PANIC: + case FLUID_ERR: + return LogLevel::ERROR; + + case FLUID_WARN: + return LogLevel::WARNING; + + case FLUID_INFO: + return LogLevel::INFO; + + case FLUID_DBG: + case LAST_LOG_LEVEL: + return LogLevel::DEBUG; + } + + /* invalid fluidsynth log level */ + return LogLevel::INFO; +} + +/** + * The fluidsynth logging callback. It forwards messages to the GLib + * logging library. + */ +static void +fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data) +{ + Log(fluidsynth_domain, + fluidsynth_level_to_mpd(fluid_log_level(level)), + message); +} + +static bool +fluidsynth_init(const config_param ¶m) +{ + Error error; + + sample_rate = param.GetBlockValue("sample_rate", 48000u); + if (!audio_check_sample_rate(sample_rate, error)) { + LogError(error); + return false; + } + + soundfont_path = param.GetBlockValue("soundfont", + "/usr/share/sounds/sf2/FluidR3_GM.sf2"); + + fluid_set_log_function(LAST_LOG_LEVEL, + fluidsynth_mpd_log_function, nullptr); + + return true; +} + +static void +fluidsynth_file_decode(Decoder &decoder, Path path_fs) +{ + char setting_sample_rate[] = "synth.sample-rate"; + /* + char setting_verbose[] = "synth.verbose"; + char setting_yes[] = "yes"; + */ + fluid_settings_t *settings; + fluid_synth_t *synth; + fluid_player_t *player; + int ret; + + /* set up fluid settings */ + + settings = new_fluid_settings(); + if (settings == nullptr) + return; + + fluid_settings_setnum(settings, setting_sample_rate, sample_rate); + + /* + fluid_settings_setstr(settings, setting_verbose, setting_yes); + */ + + /* create the fluid synth */ + + synth = new_fluid_synth(settings); + if (synth == nullptr) { + delete_fluid_settings(settings); + return; + } + + ret = fluid_synth_sfload(synth, soundfont_path, true); + if (ret < 0) { + LogWarning(fluidsynth_domain, "fluid_synth_sfload() failed"); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* create the fluid player */ + + player = new_fluid_player(synth); + if (player == nullptr) { + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + ret = fluid_player_add(player, path_fs.c_str()); + if (ret != 0) { + LogWarning(fluidsynth_domain, "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) { + LogWarning(fluidsynth_domain, "fluid_player_play() failed"); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* initialization complete - announce the audio format to the + MPD core */ + + const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); + decoder_initialized(decoder, audio_format, false, + SignedSongTime::Negative()); + + DecoderCommand cmd; + while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { + int16_t buffer[2048]; + const unsigned max_frames = ARRAY_SIZE(buffer) / 2; + + /* 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); + if (ret != 0) + break; + + cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer), + 0); + if (cmd != DecoderCommand::NONE) + break; + } + + /* clean up */ + + fluid_player_stop(player); + fluid_player_join(player); + + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); +} + +static bool +fluidsynth_scan_file(Path path_fs, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + return fluid_is_midifile(path_fs.c_str()); +} + +static const char *const fluidsynth_suffixes[] = { + "mid", + nullptr +}; + +const struct DecoderPlugin fluidsynth_decoder_plugin = { + "fluidsynth", + fluidsynth_init, + nullptr, + nullptr, + fluidsynth_file_decode, + fluidsynth_scan_file, + nullptr, + nullptr, + fluidsynth_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.hxx b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx new file mode 100644 index 000000000..cd8ec2d62 --- /dev/null +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_FLUIDSYNTH_HXX +#define MPD_DECODER_FLUIDSYNTH_HXX + +extern const struct DecoderPlugin fluidsynth_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx new file mode 100644 index 000000000..cc6ce5e5d --- /dev/null +++ b/src/decoder/plugins/GmeDecoderPlugin.cxx @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2003-2014 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 "GmeDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Alloc.hxx" +#include "util/FormatString.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <gme/gme.h> + +#define SUBTUNE_PREFIX "tune_" + +static constexpr Domain gme_domain("gme"); + +static constexpr unsigned GME_SAMPLE_RATE = 44100; +static constexpr unsigned GME_CHANNELS = 2; +static constexpr unsigned GME_BUFFER_FRAMES = 2048; +static constexpr unsigned GME_BUFFER_SAMPLES = + GME_BUFFER_FRAMES * GME_CHANNELS; + +/** + * returns the file path stripped of any /tune_xxx.* subtune + * suffix + */ +static char * +get_container_name(Path path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + char *path_container = xstrdup(path_fs.c_str()); + + char pat[64]; + snprintf(pat, sizeof(pat), "%s%s", + "*/" SUBTUNE_PREFIX "???.", + subtune_suffix); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + if (!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, nullptr)) { + g_pattern_spec_free(path_with_subtune); + return path_container; + } + + char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if (ptr != nullptr) + *ptr='\0'; + + g_pattern_spec_free(path_with_subtune); + return path_container; +} + +/** + * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune + * is appended. + */ +static int +get_song_num(Path path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + + char pat[64]; + snprintf(pat, sizeof(pat), "%s%s", + "*/" SUBTUNE_PREFIX "???.", + subtune_suffix); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + + if (g_pattern_match(path_with_subtune, + path_fs.length(), path_fs.data(), nullptr)) { + char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX); + g_pattern_spec_free(path_with_subtune); + if (!sub) + return 0; + + sub += strlen("/" SUBTUNE_PREFIX); + int song_num = strtol(sub, nullptr, 10); + + return song_num - 1; + } else { + g_pattern_spec_free(path_with_subtune); + return 0; + } +} + +static char * +gme_container_scan(Path path_fs, const unsigned int tnum) +{ + Music_Emu *emu; + const char *gme_err = gme_open_file(path_fs.c_str(), &emu, + GME_SAMPLE_RATE); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return nullptr; + } + + const unsigned num_songs = gme_track_count(emu); + gme_delete(emu); + /* if it only contains a single tune, don't treat as container */ + if (num_songs < 2) + return nullptr; + + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + if (tnum <= num_songs){ + return FormatNew(SUBTUNE_PREFIX "%03u.%s", + tnum, subtune_suffix); + } else + return nullptr; +} + +static void +gme_file_decode(Decoder &decoder, Path path_fs) +{ + char *path_container = get_container_name(path_fs); + + Music_Emu *emu; + const char *gme_err = + gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + free(path_container); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return; + } + + gme_info_t *ti; + const int song_num = get_song_num(path_fs); + gme_err = gme_track_info(emu, &ti, song_num); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + gme_delete(emu); + return; + } + + const SignedSongTime song_len = ti->length > 0 + ? SignedSongTime::FromMS(ti->length) + : SignedSongTime::Negative(); + + /* initialize the MPD decoder */ + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE, + SampleFormat::S16, GME_CHANNELS, + error)) { + LogError(error); + gme_free_info(ti); + gme_delete(emu); + return; + } + + decoder_initialized(decoder, audio_format, true, song_len); + + gme_err = gme_start_track(emu, song_num); + if (gme_err != nullptr) + LogWarning(gme_domain, gme_err); + + if (ti->length > 0) + gme_set_fade(emu, ti->length); + + /* play */ + DecoderCommand cmd; + do { + short buf[GME_BUFFER_SAMPLES]; + gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return; + } + + cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0); + if (cmd == DecoderCommand::SEEK) { + unsigned where = decoder_seek_time(decoder).ToMS(); + gme_err = gme_seek(emu, where); + if (gme_err != nullptr) + LogWarning(gme_domain, gme_err); + decoder_command_finished(decoder); + } + + if (gme_track_ended(emu)) + break; + } while (cmd != DecoderCommand::STOP); + + gme_free_info(ti); + gme_delete(emu); +} + +static bool +gme_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + char *path_container = get_container_name(path_fs); + + Music_Emu *emu; + const char *gme_err = + gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + free(path_container); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return false; + } + + const int song_num = get_song_num(path_fs); + + gme_info_t *ti; + gme_err = gme_track_info(emu, &ti, song_num); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + gme_delete(emu); + return false; + } + + assert(ti != nullptr); + + if (ti->length > 0) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime::FromMS(ti->length)); + + if (ti->song != nullptr) { + if (gme_track_count(emu) > 1) { + /* start numbering subtunes from 1 */ + char tag_title[1024]; + snprintf(tag_title, sizeof(tag_title), + "%s (%d/%d)", + ti->song, song_num + 1, + gme_track_count(emu)); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); + } else + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, ti->song); + } + + if (ti->author != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ARTIST, ti->author); + + if (ti->game != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ALBUM, ti->game); + + if (ti->comment != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_COMMENT, ti->comment); + + if (ti->copyright != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_DATE, ti->copyright); + + gme_free_info(ti); + gme_delete(emu); + + return true; +} + +static const char *const gme_suffixes[] = { + "ay", "gbs", "gym", "hes", "kss", "nsf", + "nsfe", "sap", "spc", "vgm", "vgz", + nullptr +}; + +extern const struct DecoderPlugin gme_decoder_plugin; +const struct DecoderPlugin gme_decoder_plugin = { + "gme", + nullptr, + nullptr, + nullptr, + gme_file_decode, + gme_scan_file, + nullptr, + gme_container_scan, + gme_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/GmeDecoderPlugin.hxx b/src/decoder/plugins/GmeDecoderPlugin.hxx new file mode 100644 index 000000000..f4885b6e4 --- /dev/null +++ b/src/decoder/plugins/GmeDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_GME_HXX +#define MPD_DECODER_GME_HXX + +extern const struct DecoderPlugin gme_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx new file mode 100644 index 000000000..de6c9b127 --- /dev/null +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -0,0 +1,1104 @@ +/* + * Copyright (C) 2003-2014 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 "MadDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "config/ConfigGlobal.hxx" +#include "tag/TagId3.hxx" +#include "tag/TagRva2.hxx" +#include "tag/TagHandler.hxx" +#include "tag/ReplayGain.hxx" +#include "tag/MixRamp.hxx" +#include "CheckAudioFormat.hxx" +#include "util/StringUtil.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mad.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +static constexpr unsigned long FRAMES_CUSHION = 2000; + +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 */ +static constexpr unsigned DECODERDELAY = 529; + +static constexpr bool DEFAULT_GAPLESS_MP3_PLAYBACK = true; + +static constexpr Domain mad_domain("mad"); + +static bool gapless_playback; + +gcc_const +static SongTime +ToSongTime(mad_timer_t t) +{ + return SongTime::FromMS(mad_timer_count(t, MAD_UNITS_MILLISECONDS)); +} + +static inline int32_t +mad_fixed_to_24_sample(mad_fixed_t sample) +{ + static constexpr unsigned bits = 24; + static constexpr mad_fixed_t MIN = -MAD_F_ONE; + static constexpr mad_fixed_t MAX = MAD_F_ONE - 1; + + /* round */ + sample = sample + (1L << (MAD_F_FRACBITS - bits)); + + /* clip */ + if (gcc_unlikely(sample > MAX)) + sample = MAX; + else if (gcc_unlikely(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) +{ + for (unsigned i = start; i < end; ++i) + for (unsigned c = 0; c < num_channels; ++c) + *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); +} + +static bool +mp3_plugin_init(gcc_unused const config_param ¶m) +{ + gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, + DEFAULT_GAPLESS_MP3_PLAYBACK); + return true; +} + +struct MadDecoder { + static constexpr size_t READ_BUFFER_SIZE = 40960; + static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048; + + 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]; + SignedSongTime total_time; + SongTime elapsed_time; + SongTime seek_time; + 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_first_frame; + bool decoded_first_frame; + unsigned long bit_rate; + Decoder *const decoder; + InputStream &input_stream; + enum mad_layer layer; + + MadDecoder(Decoder *decoder, InputStream &input_stream); + ~MadDecoder(); + + bool Seek(long offset); + bool FillBuffer(); + void ParseId3(size_t tagsize, Tag **mpd_tag); + enum mp3_action DecodeNextFrameHeader(Tag **tag); + enum mp3_action DecodeNextFrame(); + + gcc_pure + offset_type ThisFrameOffset() const; + + gcc_pure + offset_type RestIncludingThisFrame() const; + + /** + * Attempt to calulcate the length of the song from filesize + */ + void FileSizeToSongLength(); + + bool DecodeFirstFrame(Tag **tag); + + gcc_pure + long TimeToFrame(SongTime t) const; + + void UpdateTimerNextFrame(); + + /** + * Sends the synthesized current frame via decoder_data(). + */ + DecoderCommand SendPCM(unsigned i, unsigned pcm_length); + + /** + * Synthesize the current frame and send it via + * decoder_data(). + */ + DecoderCommand SyncAndSend(); + + bool Read(); +}; + +MadDecoder::MadDecoder(Decoder *_decoder, + InputStream &_input_stream) + :mute_frame(MUTEFRAME_NONE), + frame_offsets(nullptr), + times(nullptr), + highest_frame(0), max_frames(0), current_frame(0), + drop_start_frames(0), drop_end_frames(0), + drop_start_samples(0), drop_end_samples(0), + found_replay_gain(false), + found_first_frame(false), decoded_first_frame(false), + decoder(_decoder), input_stream(_input_stream), + layer(mad_layer(0)) +{ + mad_stream_init(&stream); + mad_stream_options(&stream, MAD_OPTION_IGNORECRC); + mad_frame_init(&frame); + mad_synth_init(&synth); + mad_timer_reset(&timer); +} + +inline bool +MadDecoder::Seek(long offset) +{ + Error error; + if (!input_stream.LockSeek(offset, error)) + return false; + + mad_stream_buffer(&stream, input_buffer, 0); + stream.error = MAD_ERROR_NONE; + + return true; +} + +inline bool +MadDecoder::FillBuffer() +{ + size_t remaining, length; + unsigned char *dest; + + if (stream.next_frame != nullptr) { + remaining = stream.bufend - stream.next_frame; + memmove(input_buffer, stream.next_frame, remaining); + dest = input_buffer + remaining; + length = READ_BUFFER_SIZE - remaining; + } else { + remaining = 0; + length = READ_BUFFER_SIZE; + dest = 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(decoder, input_stream, dest, length); + if (length == 0) + return false; + + mad_stream_buffer(&stream, input_buffer, length + remaining); + stream.error = MAD_ERROR_NONE; + + return true; +} + +#ifdef HAVE_ID3TAG +static bool +parse_id3_replay_gain_info(ReplayGainInfo &rgi, + struct id3_tag *tag) +{ + bool found = false; + + rgi.Clear(); + + struct id3_frame *frame; + for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + char *const key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + char *const value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (ParseReplayGainTag(rgi, key, value)) + found = true; + + free(key); + free(value); + } + + return found || + /* fall back on RVA2 if no replaygain tags found */ + tag_rva2_parse(tag, rgi); +} +#endif + +#ifdef HAVE_ID3TAG +gcc_pure +static MixRampInfo +parse_id3_mixramp(struct id3_tag *tag) +{ + MixRampInfo result; + + struct id3_frame *frame; + for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + char *const key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + char *const value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + ParseMixRampTag(result, key, value); + + free(key); + free(value); + } + + return result; +} +#endif + +inline void +MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) +{ +#ifdef HAVE_ID3TAG + id3_byte_t *allocated = nullptr; + + const id3_length_t count = stream.bufend - stream.this_frame; + + const id3_byte_t *id3_data; + if (tagsize <= count) { + id3_data = stream.this_frame; + mad_stream_skip(&(stream), tagsize); + } else { + allocated = new id3_byte_t[tagsize]; + memcpy(allocated, stream.this_frame, count); + mad_stream_skip(&(stream), count); + + if (!decoder_read_full(decoder, input_stream, + allocated + count, tagsize - count)) { + LogDebug(mad_domain, "error parsing ID3 tag"); + delete[] allocated; + return; + } + + id3_data = allocated; + } + + struct id3_tag *const id3_tag = id3_tag_parse(id3_data, tagsize); + if (id3_tag == nullptr) { + delete[] allocated; + return; + } + + if (mpd_tag) { + Tag *tmp_tag = tag_id3_import(id3_tag); + if (tmp_tag != nullptr) { + delete *mpd_tag; + *mpd_tag = tmp_tag; + } + } + + if (decoder != nullptr) { + ReplayGainInfo rgi; + + if (parse_id3_replay_gain_info(rgi, id3_tag)) { + decoder_replay_gain(*decoder, &rgi); + found_replay_gain = true; + } + + decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag)); + } + + id3_tag_delete(id3_tag); + + delete[] 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. */ + + size_t count = stream.bufend - stream.this_frame; + + if (tagsize <= count) { + mad_stream_skip(&stream, tagsize); + } else { + mad_stream_skip(&stream, count); + decoder_skip(decoder, input_stream, tagsize - count); + } +#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 length + * of the ID3 frame. + */ +static signed long +id3_tag_query(const void *p0, size_t length) +{ + const char *p = (const char *)p0; + + return length >= 10 && memcmp(p, "ID3", 3) == 0 + ? (p[8] << 7) + p[9] + 10 + : 0; +} +#endif /* !HAVE_ID3TAG */ + +static enum mp3_action +RecoverFrameError(struct mad_stream &stream) +{ + if (MAD_RECOVERABLE(stream.error)) + return DECODE_SKIP; + else if (stream.error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + + FormatWarning(mad_domain, + "unrecoverable frame level error: %s", + mad_stream_errorstr(&stream)); + return DECODE_BREAK; +} + +enum mp3_action +MadDecoder::DecodeNextFrameHeader(Tag **tag) +{ + if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && + !FillBuffer()) + return DECODE_BREAK; + + if (mad_header_decode(&frame.header, &stream)) { + if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) { + signed long tagsize = id3_tag_query(stream.this_frame, + stream.bufend - + stream.this_frame); + + if (tagsize > 0) { + if (tag && !(*tag)) { + ParseId3((size_t)tagsize, tag); + } else { + mad_stream_skip(&stream, tagsize); + } + return DECODE_CONT; + } + } + + return RecoverFrameError(stream); + } + + enum mad_layer new_layer = frame.header.layer; + if (layer == (mad_layer)0) { + if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) { + /* Only layer 2 and 3 have been tested to work */ + return DECODE_SKIP; + } + + layer = new_layer; + } else if (new_layer != layer) { + /* Don't decode frames with a different layer than the first */ + return DECODE_SKIP; + } + + return DECODE_OK; +} + +enum mp3_action +MadDecoder::DecodeNextFrame() +{ + if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && + !FillBuffer()) + return DECODE_BREAK; + + if (mad_frame_decode(&frame, &stream)) { + if (stream.error == MAD_ERROR_LOSTSYNC) { + signed long tagsize = id3_tag_query(stream.this_frame, + stream.bufend - + stream.this_frame); + if (tagsize > 0) { + mad_stream_skip(&stream, tagsize); + return DECODE_CONT; + } + } + + return RecoverFrameError(stream); + } + + return DECODE_OK; +} + +/* xing stuff stolen from alsaplayer, and heavily modified by jat */ +static constexpr unsigned XI_MAGIC = (('X' << 8) | 'i'); +static constexpr unsigned NG_MAGIC = (('n' << 8) | 'g'); +static constexpr unsigned IN_MAGIC = (('I' << 8) | 'n'); +static constexpr unsigned 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 */ +}; + +static const unsigned XING_FRAMES = 1; +static const unsigned XING_BYTES = 2; +static const unsigned XING_TOC = 4; +static const unsigned XING_SCALE = 8; + +struct lame_version { + unsigned major; + unsigned minor; +}; + +struct lame { + char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ + struct lame_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) +{ + int bitlen = *oldbitlen; + + if (bitlen < 16) + return false; + + const unsigned long 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 (unsigned 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 */ + const int 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) +{ + /* 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 (unsigned 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 (!StringStartsWith(lame->encoder, "LAME")) + return false; + + if (sscanf(lame->encoder+4, "%u.%u", + &lame->version.major, &lame->version.minor) != 2) + return false; + + FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")", + 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 */ + int adj = 0; + 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 */ + FormatDebug(mad_domain, "LAME peak found: %f", lame->peak); + + lame->track_gain = 0; + unsigned name = mad_bit_read(ptr, 3); /* gain name */ + unsigned orig = mad_bit_read(ptr, 3); /* gain originator */ + unsigned sign = mad_bit_read(ptr, 1); /* sign bit */ + int gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 1 && orig != 0) { + lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; + FormatDebug(mad_domain, "LAME track gain found: %f", + 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; + FormatDebug(mad_domain, "LAME album gain found: %f", + 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); + + FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i", + lame->encoder_delay, lame->encoder_padding); + + mad_bit_read(ptr, 80); + + lame->crc = mad_bit_read(ptr, 16); + + *bitlen -= 216; + + return true; +} + +static inline SongTime +mp3_frame_duration(const struct mad_frame *frame) +{ + return ToSongTime(frame->header.duration); +} + +inline offset_type +MadDecoder::ThisFrameOffset() const +{ + auto offset = input_stream.GetOffset(); + + if (stream.this_frame != nullptr) + offset -= stream.bufend - stream.this_frame; + else + offset -= stream.bufend - stream.buffer; + + return offset; +} + +inline offset_type +MadDecoder::RestIncludingThisFrame() const +{ + return input_stream.GetSize() - ThisFrameOffset(); +} + +inline void +MadDecoder::FileSizeToSongLength() +{ + if (input_stream.KnownSize()) { + offset_type rest = RestIncludingThisFrame(); + + const SongTime frame_duration = mp3_frame_duration(&frame); + const SongTime duration = + SongTime::FromScale<uint64_t>(rest, + frame.header.bitrate / 8); + total_time = duration; + + max_frames = (frame_duration.IsPositive() + ? duration.count() / frame_duration.count() + : 0) + + FRAMES_CUSHION; + } else { + max_frames = FRAMES_CUSHION; + total_time = SignedSongTime::Negative(); + } +} + +inline bool +MadDecoder::DecodeFirstFrame(Tag **tag) +{ + struct xing xing; + xing.frames = 0; + + while (true) { + enum mp3_action ret; + do { + ret = DecodeNextFrameHeader(tag); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_SKIP) continue; + + do { + ret = DecodeNextFrame(); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_OK) break; + } + + struct mad_bitptr ptr = stream.anc_ptr; + int bitlen = stream.anc_bitlen; + + FileSizeToSongLength(); + + /* + * if an xing tag exists, use that! + */ + if (parse_xing(&xing, &ptr, &bitlen)) { + mute_frame = MUTEFRAME_SKIP; + + if ((xing.flags & XING_FRAMES) && xing.frames) { + mad_timer_t duration = frame.header.duration; + mad_timer_multiply(&duration, xing.frames); + total_time = ToSongTime(duration); + max_frames = xing.frames; + } + + struct lame lame; + if (parse_lame(&lame, &ptr, &bitlen)) { + if (gapless_playback && input_stream.IsSeekable()) { + drop_start_samples = lame.encoder_delay + + DECODERDELAY; + drop_end_samples = lame.encoder_padding; + } + + /* Album gain isn't currently used. See comment in + * parse_lame() for details. -- jat */ + if (decoder != nullptr && !found_replay_gain && + lame.track_gain) { + ReplayGainInfo rgi; + rgi.Clear(); + rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; + rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak; + decoder_replay_gain(*decoder, &rgi); + } + } + } + + if (!max_frames) + return false; + + if (max_frames > 8 * 1024 * 1024) { + FormatWarning(mad_domain, + "mp3 file header indicates too many frames: %lu", + max_frames); + return false; + } + + frame_offsets = new long[max_frames]; + times = new mad_timer_t[max_frames]; + + return true; +} + +MadDecoder::~MadDecoder() +{ + mad_synth_finish(&synth); + mad_frame_finish(&frame); + mad_stream_finish(&stream); + + delete[] frame_offsets; + delete[] times; +} + +/* this is primarily used for getting total time for tags */ +static std::pair<bool, SignedSongTime> +mad_decoder_total_file_time(InputStream &is) +{ + MadDecoder data(nullptr, is); + return data.DecodeFirstFrame(nullptr) + ? std::make_pair(true, data.total_time) + : std::make_pair(false, SignedSongTime::Negative()); +} + +long +MadDecoder::TimeToFrame(SongTime t) const +{ + unsigned long i; + + for (i = 0; i < highest_frame; ++i) { + auto frame_time = ToSongTime(times[i]); + if (frame_time >= t) + break; + } + + return i; +} + +void +MadDecoder::UpdateTimerNextFrame() +{ + if (current_frame >= highest_frame) { + /* record this frame's properties in frame_offsets + (for seeking) and times */ + bit_rate = frame.header.bitrate; + + if (current_frame >= max_frames) + /* cap current_frame */ + current_frame = max_frames - 1; + else + highest_frame++; + + frame_offsets[current_frame] = ThisFrameOffset(); + + mad_timer_add(&timer, frame.header.duration); + times[current_frame] = timer; + } else + /* get the new timer value from "times" */ + timer = times[current_frame]; + + current_frame++; + elapsed_time = ToSongTime(timer); +} + +DecoderCommand +MadDecoder::SendPCM(unsigned i, unsigned pcm_length) +{ + unsigned max_samples = sizeof(output_buffer) / + sizeof(output_buffer[0]) / + MAD_NCHANNELS(&frame.header); + + while (i < pcm_length) { + unsigned int num_samples = pcm_length - i; + if (num_samples > max_samples) + num_samples = max_samples; + + i += num_samples; + + mad_fixed_to_24_buffer(output_buffer, &synth, + i - num_samples, i, + MAD_NCHANNELS(&frame.header)); + num_samples *= MAD_NCHANNELS(&frame.header); + + auto cmd = decoder_data(*decoder, input_stream, output_buffer, + sizeof(output_buffer[0]) * num_samples, + bit_rate / 1000); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + +inline DecoderCommand +MadDecoder::SyncAndSend() +{ + mad_synth_frame(&synth, &frame); + + if (!found_first_frame) { + unsigned int samples_per_frame = synth.pcm.length; + drop_start_frames = drop_start_samples / samples_per_frame; + drop_end_frames = drop_end_samples / samples_per_frame; + drop_start_samples = drop_start_samples % samples_per_frame; + drop_end_samples = drop_end_samples % samples_per_frame; + found_first_frame = true; + } + + if (drop_start_frames > 0) { + drop_start_frames--; + return DecoderCommand::NONE; + } else if ((drop_end_frames > 0) && + (current_frame == (max_frames + 1 - drop_end_frames))) { + /* stop decoding, effectively dropping all remaining + frames */ + return DecoderCommand::STOP; + } + + unsigned i = 0; + if (!decoded_first_frame) { + i = drop_start_samples; + decoded_first_frame = true; + } + + unsigned pcm_length = synth.pcm.length; + if (drop_end_samples && + (current_frame == max_frames - drop_end_frames)) { + if (drop_end_samples >= pcm_length) + pcm_length = 0; + else + pcm_length -= drop_end_samples; + } + + auto cmd = SendPCM(i, pcm_length); + if (cmd != DecoderCommand::NONE) + return cmd; + + if (drop_end_samples && + (current_frame == max_frames - drop_end_frames)) + /* stop decoding, effectively dropping + * all remaining samples */ + return DecoderCommand::STOP; + + return DecoderCommand::NONE; +} + +inline bool +MadDecoder::Read() +{ + UpdateTimerNextFrame(); + + switch (mute_frame) { + DecoderCommand cmd; + + case MUTEFRAME_SKIP: + mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_SEEK: + if (elapsed_time >= seek_time) + mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_NONE: + cmd = SyncAndSend(); + if (cmd == DecoderCommand::SEEK) { + assert(input_stream.IsSeekable()); + + unsigned long j = + TimeToFrame(decoder_seek_time(*decoder)); + if (j < highest_frame) { + if (Seek(frame_offsets[j])) { + current_frame = j; + decoder_command_finished(*decoder); + } else + decoder_seek_error(*decoder); + } else { + seek_time = decoder_seek_time(*decoder); + mute_frame = MUTEFRAME_SEEK; + decoder_command_finished(*decoder); + } + } else if (cmd != DecoderCommand::NONE) + return false; + } + + while (true) { + enum mp3_action ret; + do { + Tag *tag = nullptr; + + ret = DecodeNextFrameHeader(&tag); + + if (tag != nullptr) { + decoder_tag(*decoder, input_stream, + std::move(*tag)); + delete tag; + } + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + + const bool skip = ret == DECODE_SKIP; + + if (mute_frame == MUTEFRAME_NONE) { + do { + ret = DecodeNextFrame(); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + } + + if (!skip && ret == DECODE_OK) + return true; + } +} + +static void +mp3_decode(Decoder &decoder, InputStream &input_stream) +{ + MadDecoder data(&decoder, input_stream); + + Tag *tag = nullptr; + if (!data.DecodeFirstFrame(&tag)) { + delete tag; + + if (decoder_get_command(decoder) == DecoderCommand::NONE) + LogError(mad_domain, + "input/Input does not appear to be a mp3 bit stream"); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + data.frame.header.samplerate, + SampleFormat::S24_P32, + MAD_NCHANNELS(&data.frame.header), + error)) { + LogError(error); + delete tag; + return; + } + + decoder_initialized(decoder, audio_format, + input_stream.IsSeekable(), + data.total_time); + + if (tag != nullptr) { + decoder_tag(decoder, input_stream, std::move(*tag)); + delete tag; + } + + while (data.Read()) {} +} + +static bool +mad_decoder_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + const auto result = mad_decoder_total_file_time(is); + if (!result.first) + return false; + + if (!result.second.IsNegative()) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime(result.second)); + return true; +} + +static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr }; +static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr }; + +const struct DecoderPlugin mad_decoder_plugin = { + "mad", + mp3_plugin_init, + nullptr, + mp3_decode, + nullptr, + nullptr, + mad_decoder_scan_stream, + nullptr, + mp3_suffixes, + mp3_mime_types, +}; diff --git a/src/decoder/plugins/MadDecoderPlugin.hxx b/src/decoder/plugins/MadDecoderPlugin.hxx new file mode 100644 index 000000000..eb2a10d6f --- /dev/null +++ b/src/decoder/plugins/MadDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_MAD_HXX +#define MPD_DECODER_MAD_HXX + +extern const struct DecoderPlugin mad_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx new file mode 100644 index 000000000..85633f1fc --- /dev/null +++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2003-2014 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 "MikmodDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "system/FatalError.hxx" +#include "fs/Path.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mikmod.h> + +#include <assert.h> + +static constexpr Domain mikmod_domain("mikmod"); + +/* this is largely copied from alsaplayer */ + +static constexpr size_t 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 = { + nullptr, + drv_name, + drv_version, + 0, + 255, +#if (LIBMIKMOD_VERSION > 0x030106) + drv_alias, +#if (LIBMIKMOD_VERSION >= 0x030200) + nullptr, /* CmdLineHelp */ +#endif + nullptr, /* CommandLine */ +#endif + mikmod_mpd_is_present, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + mikmod_mpd_init, + mikmod_mpd_exit, + nullptr, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + mikmod_mpd_update, + nullptr, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + +static bool mikmod_loop; +static unsigned mikmod_sample_rate; + +static bool +mikmod_decoder_init(const config_param ¶m) +{ + static char params[] = ""; + + mikmod_loop = param.GetBlockValue("loop", false); + mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u); + if (!audio_valid_sample_rate(mikmod_sample_rate)) + FormatFatalError("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)) { + FormatError(mikmod_domain, + "Could not init MikMod: %s", + MikMod_strerror(MikMod_errno)); + return false; + } + + return true; +} + +static void +mikmod_decoder_finish(void) +{ + MikMod_Exit(); +} + +static void +mikmod_decoder_file_decode(Decoder &decoder, Path path_fs) +{ + /* deconstify the path because libmikmod wants a non-const + string pointer */ + char *const path2 = const_cast<char *>(path_fs.c_str()); + + MODULE *handle; + int ret; + SBYTE buffer[MIKMOD_FRAME_SIZE]; + + handle = Player_Load(path2, 128, 0); + + if (handle == nullptr) { + FormatError(mikmod_domain, + "failed to open mod: %s", path_fs.c_str()); + return; + } + + handle->loop = mikmod_loop; + + const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, false, + SignedSongTime::Negative()); + + Player_Start(handle); + + DecoderCommand cmd = DecoderCommand::NONE; + while (cmd == DecoderCommand::NONE && Player_Active()) { + ret = VC_WriteBytes(buffer, sizeof(buffer)); + cmd = decoder_data(decoder, nullptr, buffer, ret, 0); + } + + Player_Stop(); + Player_Free(handle); +} + +static bool +mikmod_decoder_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + /* deconstify the path because libmikmod wants a non-const + string pointer */ + char *const path2 = const_cast<char *>(path_fs.c_str()); + + MODULE *handle = Player_Load(path2, 128, 0); + + if (handle == nullptr) { + FormatDebug(mikmod_domain, + "Failed to open file: %s", path_fs.c_str()); + return false; + } + + Player_Free(handle); + + char *title = Player_LoadTitle(path2); + if (title != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); +#if (LIBMIKMOD_VERSION >= 0x030200) + MikMod_free(title); +#else + free(title); +#endif + } + + return true; +} + +static const char *const mikmod_decoder_suffixes[] = { + "amf", + "dsm", + "far", + "gdm", + "imf", + "it", + "med", + "mod", + "mtm", + "s3m", + "stm", + "stx", + "ult", + "uni", + "xm", + nullptr +}; + +const struct DecoderPlugin mikmod_decoder_plugin = { + "mikmod", + mikmod_decoder_init, + mikmod_decoder_finish, + nullptr, + mikmod_decoder_file_decode, + mikmod_decoder_scan_file, + nullptr, + nullptr, + mikmod_decoder_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/MikmodDecoderPlugin.hxx b/src/decoder/plugins/MikmodDecoderPlugin.hxx new file mode 100644 index 000000000..27ba2a823 --- /dev/null +++ b/src/decoder/plugins/MikmodDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_MIKMOD_HXX +#define MPD_DECODER_MIKMOD_HXX + +extern const struct DecoderPlugin mikmod_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/ModplugDecoderPlugin.cxx b/src/decoder/plugins/ModplugDecoderPlugin.cxx new file mode 100644 index 000000000..3e0a41550 --- /dev/null +++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2003-2014 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 "ModplugDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "tag/TagHandler.hxx" +#include "system/FatalError.hxx" +#include "util/WritableBuffer.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <libmodplug/modplug.h> + + +#include <assert.h> + +static constexpr Domain modplug_domain("modplug"); + +static constexpr size_t MODPLUG_FRAME_SIZE = 4096; +static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024; +static constexpr offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; + +static int modplug_loop_count; + +static bool +modplug_decoder_init(const config_param ¶m) +{ + modplug_loop_count = param.GetBlockValue("loop_count", 0); + if (modplug_loop_count < -1) + FormatFatalError("Invalid loop count in line %d: %i", + param.line, modplug_loop_count); + + return true; +} + +static WritableBuffer<uint8_t> +mod_loadfile(Decoder *decoder, InputStream &is) +{ + //known/unknown size, preallocate array, lets read in chunks + + const bool is_stream = !is.KnownSize(); + + WritableBuffer<uint8_t> buffer; + if (is_stream) + buffer.size = MODPLUG_PREALLOC_BLOCK; + else { + const auto size = is.GetSize(); + + if (size == 0) { + LogWarning(modplug_domain, "file is empty"); + return { nullptr, 0 }; + } + + if (size > MODPLUG_FILE_LIMIT) { + LogWarning(modplug_domain, "file too large"); + return { nullptr, 0 }; + } + + buffer.size = size; + } + + buffer.data = new uint8_t[buffer.size]; + + uint8_t *const end = buffer.end(); + uint8_t *p = buffer.begin(); + + while (true) { + size_t ret = decoder_read(decoder, is, p, end - p); + if (ret == 0) { + if (is.LockIsEOF()) + /* end of file */ + break; + + /* I/O error - skip this song */ + delete[] buffer.data; + buffer.data = nullptr; + return buffer; + } + + p += ret; + if (p == end) { + if (!is_stream) + break; + + LogWarning(modplug_domain, "stream too large"); + delete[] buffer.data; + buffer.data = nullptr; + return buffer; + } + } + + buffer.size = p - buffer.data; + return buffer; +} + +static ModPlugFile * +LoadModPlugFile(Decoder *decoder, InputStream &is) +{ + const auto buffer = mod_loadfile(decoder, is); + if (buffer.IsNull()) { + LogWarning(modplug_domain, "could not load stream"); + return nullptr; + } + + ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size); + delete[] buffer.data; + return f; +} + +static void +mod_decode(Decoder &decoder, InputStream &is) +{ + ModPlug_Settings settings; + int ret; + char audio_buffer[MODPLUG_FRAME_SIZE]; + + ModPlug_GetSettings(&settings); + /* alter setting */ + settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ + settings.mChannels = 2; + settings.mBits = 16; + settings.mFrequency = 44100; + settings.mLoopCount = modplug_loop_count; + /* insert more setting changes here */ + ModPlug_SetSettings(&settings); + + ModPlugFile *f = LoadModPlugFile(&decoder, is); + if (f == nullptr) { + LogWarning(modplug_domain, "could not decode stream"); + return; + } + + static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, + is.IsSeekable(), + SongTime::FromMS(ModPlug_GetLength(f))); + + DecoderCommand cmd; + do { + ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); + if (ret <= 0) + break; + + cmd = decoder_data(decoder, nullptr, + audio_buffer, ret, + 0); + + if (cmd == DecoderCommand::SEEK) { + ModPlug_Seek(f, decoder_seek_time(decoder).ToMS()); + decoder_command_finished(decoder); + } + + } while (cmd != DecoderCommand::STOP); + + ModPlug_Unload(f); +} + +static bool +modplug_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + ModPlugFile *f = LoadModPlugFile(nullptr, is); + if (f == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + SongTime::FromMS(ModPlug_GetLength(f))); + + const char *title = ModPlug_GetName(f); + if (title != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); + + ModPlug_Unload(f); + + return true; +} + +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", + nullptr +}; + +const struct DecoderPlugin modplug_decoder_plugin = { + "modplug", + modplug_decoder_init, + nullptr, + mod_decode, + nullptr, + nullptr, + modplug_scan_stream, + nullptr, + mod_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/ModplugDecoderPlugin.hxx b/src/decoder/plugins/ModplugDecoderPlugin.hxx new file mode 100644 index 000000000..08f2ecb12 --- /dev/null +++ b/src/decoder/plugins/ModplugDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_MODPLUG_HXX +#define MPD_DECODER_MODPLUG_HXX + +extern const struct DecoderPlugin modplug_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.cxx b/src/decoder/plugins/MpcdecDecoderPlugin.cxx new file mode 100644 index 000000000..befed0f3b --- /dev/null +++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2003-2014 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 "MpcdecDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <mpc/mpcdec.h> + +#include <math.h> + +struct mpc_decoder_data { + InputStream &is; + Decoder *decoder; + + mpc_decoder_data(InputStream &_is, Decoder *_decoder) + :is(_is), decoder(_decoder) {} +}; + +static constexpr Domain mpcdec_domain("mpcdec"); + +static mpc_int32_t +mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return decoder_read(data->decoder, data->is, ptr, size); +} + +static mpc_bool_t +mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is.LockSeek(offset, IgnoreError()); +} + +static mpc_int32_t +mpc_tell_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return (long)data->is.GetOffset(); +} + +static mpc_bool_t +mpc_canseek_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is.IsSeekable(); +} + +static mpc_int32_t +mpc_getsize_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + if (!data->is.KnownSize()) + return -1; + + return data->is.GetSize(); +} + +/* 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(Decoder &mpd_decoder, InputStream &is) +{ + MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; + + mpc_decoder_data data(is, &mpd_decoder); + + mpc_reader reader; + 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; + + mpc_demux *demux = mpc_demux_init(&reader); + if (demux == nullptr) { + if (decoder_get_command(mpd_decoder) != DecoderCommand::STOP) + LogWarning(mpcdec_domain, + "Not a valid musepack stream"); + return; + } + + mpc_streaminfo info; + mpc_demux_get_info(demux, &info); + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, info.sample_freq, + SampleFormat::S24_P32, + info.channels, error)) { + LogError(error); + mpc_demux_exit(demux); + return; + } + + ReplayGainInfo rgi; + rgi.Clear(); + rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); + rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; + rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); + rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; + + decoder_replay_gain(mpd_decoder, &rgi); + + decoder_initialized(mpd_decoder, audio_format, + is.IsSeekable(), + SongTime::FromS(mpc_streaminfo_get_length(&info))); + + DecoderCommand cmd = DecoderCommand::NONE; + do { + if (cmd == DecoderCommand::SEEK) { + mpc_int64_t where = + decoder_seek_where_frame(mpd_decoder); + bool success; + + success = mpc_demux_seek_sample(demux, where) + == MPC_STATUS_OK; + if (success) + decoder_command_finished(mpd_decoder); + else + decoder_seek_error(mpd_decoder); + } + + mpc_uint32_t vbr_update_bits = 0; + + mpc_frame_info frame; + frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; + mpc_status status = mpc_demux_decode(demux, &frame); + if (status != MPC_STATUS_OK) { + LogWarning(mpcdec_domain, + "Failed to decode sample"); + break; + } + + if (frame.bits == -1) + break; + + mpc_uint32_t ret = frame.samples; + ret *= info.channels; + + int32_t chunk[ARRAY_SIZE(sample_buffer)]; + mpc_to_mpd_buffer(chunk, sample_buffer, ret); + + long 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 != DecoderCommand::STOP); + + mpc_demux_exit(demux); +} + +static SignedSongTime +mpcdec_get_file_duration(InputStream &is) +{ + mpc_decoder_data data(is, nullptr); + + mpc_reader reader; + 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; + + mpc_demux *demux = mpc_demux_init(&reader); + if (demux == nullptr) + return SignedSongTime::Negative(); + + mpc_streaminfo info; + mpc_demux_get_info(demux, &info); + mpc_demux_exit(demux); + + return SongTime::FromS(mpc_streaminfo_get_length(&info)); +} + +static bool +mpcdec_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + const auto duration = mpcdec_get_file_duration(is); + if (duration.IsNegative()) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); + return true; +} + +static const char *const mpcdec_suffixes[] = { "mpc", nullptr }; + +const struct DecoderPlugin mpcdec_decoder_plugin = { + "mpcdec", + nullptr, + nullptr, + mpcdec_decode, + nullptr, + nullptr, + mpcdec_scan_stream, + nullptr, + mpcdec_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.hxx b/src/decoder/plugins/MpcdecDecoderPlugin.hxx new file mode 100644 index 000000000..7f71311fa --- /dev/null +++ b/src/decoder/plugins/MpcdecDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_MPCDEC_HXX +#define MPD_DECODER_MPCDEC_HXX + +extern const struct DecoderPlugin mpcdec_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx new file mode 100644 index 000000000..166529a4d --- /dev/null +++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2003-2014 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 "Mpg123DecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/ReplayGain.hxx" +#include "tag/MixRamp.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mpg123.h> + +#include <stdio.h> + +static constexpr Domain mpg123_domain("mpg123"); + +static bool +mpd_mpg123_init(gcc_unused const config_param ¶m) +{ + mpg123_init(); + + return true; +} + +static void +mpd_mpg123_finish(void) +{ + mpg123_exit(); +} + +/** + * Opens a file with an existing #mpg123_handle. + * + * @param handle a handle which was created before; on error, this + * function will not free it + * @param audio_format this parameter is filled after successful + * return + * @return true on success + */ +static bool +mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, + AudioFormat &audio_format) +{ + /* mpg123_open() wants a writable string :-( */ + char *const path2 = const_cast<char *>(path_fs); + + int error = mpg123_open(handle, path2); + if (error != MPG123_OK) { + FormatWarning(mpg123_domain, + "libmpg123 failed to open %s: %s", + path_fs, mpg123_plain_strerror(error)); + return false; + } + + /* obtain the audio format */ + + long rate; + int channels, encoding; + error = mpg123_getformat(handle, &rate, &channels, &encoding); + if (error != MPG123_OK) { + FormatWarning(mpg123_domain, + "mpg123_getformat() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + if (encoding != MPG123_ENC_SIGNED_16) { + /* other formats not yet implemented */ + FormatWarning(mpg123_domain, + "expected MPG123_ENC_SIGNED_16, got %d", + encoding); + return false; + } + + Error error2; + if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16, + channels, error2)) { + LogError(error2); + return false; + } + + return true; +} + +static void +AddTagItem(TagBuilder &tag, TagType type, const mpg123_string &s) +{ + assert(s.p != nullptr); + assert(s.size >= s.fill); + assert(s.fill > 0); + + tag.AddItem(type, s.p, s.fill - 1); +} + +static void +AddTagItem(TagBuilder &tag, TagType type, const mpg123_string *s) +{ + if (s != nullptr) + AddTagItem(tag, type, *s); +} + +static void +mpd_mpg123_id3v2_tag(Decoder &decoder, const mpg123_id3v2 &id3v2) +{ + TagBuilder tag; + + AddTagItem(tag, TAG_TITLE, id3v2.title); + AddTagItem(tag, TAG_ARTIST, id3v2.artist); + AddTagItem(tag, TAG_ALBUM, id3v2.album); + AddTagItem(tag, TAG_DATE, id3v2.year); + AddTagItem(tag, TAG_GENRE, id3v2.genre); + + for (size_t i = 0, n = id3v2.comments; i < n; ++i) + AddTagItem(tag, TAG_COMMENT, id3v2.comment_list[i].text); + + decoder_tag(decoder, nullptr, tag.Commit()); +} + +static void +mpd_mpg123_id3v2_extras(Decoder &decoder, const mpg123_id3v2 &id3v2) +{ + ReplayGainInfo replay_gain; + replay_gain.Clear(); + + MixRampInfo mix_ramp; + + bool found_replay_gain = false, found_mixramp = false; + + for (size_t i = 0, n = id3v2.extras; i < n; ++i) { + if (ParseReplayGainTag(replay_gain, + id3v2.extra[i].description.p, + id3v2.extra[i].text.p)) + found_replay_gain = true; + else if (ParseMixRampTag(mix_ramp, + id3v2.extra[i].description.p, + id3v2.extra[i].text.p)) + found_mixramp = true; + } + + if (found_replay_gain) + decoder_replay_gain(decoder, &replay_gain); + + if (found_mixramp) + decoder_mixramp(decoder, std::move(mix_ramp)); +} + +static void +mpd_mpg123_id3v2(Decoder &decoder, const mpg123_id3v2 &id3v2) +{ + mpd_mpg123_id3v2_tag(decoder, id3v2); + mpd_mpg123_id3v2_extras(decoder, id3v2); +} + +static void +mpd_mpg123_meta(Decoder &decoder, mpg123_handle *const handle) +{ + if ((mpg123_meta_check(handle) & MPG123_NEW_ID3) == 0) + return; + + mpg123_id3v1 *v1; + mpg123_id3v2 *v2; + if (mpg123_id3(handle, &v1, &v2) != MPG123_OK) + return; + + if (v2 != nullptr) + mpd_mpg123_id3v2(decoder, *v2); +} + +static void +mpd_mpg123_file_decode(Decoder &decoder, Path path_fs) +{ + /* open the file */ + + int error; + mpg123_handle *const handle = mpg123_new(nullptr, &error); + if (handle == nullptr) { + FormatError(mpg123_domain, + "mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return; + } + + AudioFormat audio_format; + if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) { + mpg123_delete(handle); + return; + } + + const off_t num_samples = mpg123_length(handle); + + /* tell MPD core we're ready */ + + const auto duration = + SongTime::FromScale<uint64_t>(num_samples, + audio_format.sample_rate); + + decoder_initialized(decoder, audio_format, true, duration); + + struct mpg123_frameinfo info; + if (mpg123_info(handle, &info) != MPG123_OK) { + info.vbr = MPG123_CBR; + info.bitrate = 0; + } + + switch (info.vbr) { + case MPG123_ABR: + info.bitrate = info.abr_rate; + break; + case MPG123_CBR: + break; + default: + info.bitrate = 0; + } + + /* the decoder main loop */ + DecoderCommand cmd; + do { + /* read metadata */ + mpd_mpg123_meta(decoder, handle); + + /* decode */ + + unsigned char buffer[8192]; + size_t nbytes; + error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); + if (error != MPG123_OK) { + if (error != MPG123_DONE) + FormatWarning(mpg123_domain, + "mpg123_read() failed: %s", + mpg123_plain_strerror(error)); + break; + } + + /* update bitrate for ABR/VBR */ + if (info.vbr != MPG123_CBR) { + /* FIXME: maybe skip, as too expensive? */ + /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ + if (mpg123_info (handle, &info) != MPG123_OK) + info.bitrate = 0; + } + + /* send to MPD */ + + cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate); + + if (cmd == DecoderCommand::SEEK) { + off_t c = decoder_seek_where_frame(decoder); + c = mpg123_seek(handle, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else { + decoder_command_finished(decoder); + decoder_timestamp(decoder, c/(double)audio_format.sample_rate); + } + + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + /* cleanup */ + + mpg123_delete(handle); +} + +static bool +mpd_mpg123_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + int error; + mpg123_handle *const handle = mpg123_new(nullptr, &error); + if (handle == nullptr) { + FormatError(mpg123_domain, + "mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + AudioFormat audio_format; + if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) { + mpg123_delete(handle); + return false; + } + + const off_t num_samples = mpg123_length(handle); + if (num_samples <= 0) { + mpg123_delete(handle); + return false; + } + + /* ID3 tag support not yet implemented */ + + mpg123_delete(handle); + + const auto duration = + SongTime::FromScale<uint64_t>(num_samples, + audio_format.sample_rate); + + tag_handler_invoke_duration(handler, handler_ctx, duration); + return true; +} + +static const char *const mpg123_suffixes[] = { + "mp3", + nullptr +}; + +const struct DecoderPlugin mpg123_decoder_plugin = { + "mpg123", + mpd_mpg123_init, + mpd_mpg123_finish, + /* streaming not yet implemented */ + nullptr, + mpd_mpg123_file_decode, + mpd_mpg123_scan_file, + nullptr, + nullptr, + mpg123_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/Mpg123DecoderPlugin.hxx b/src/decoder/plugins/Mpg123DecoderPlugin.hxx new file mode 100644 index 000000000..fd089c6a4 --- /dev/null +++ b/src/decoder/plugins/Mpg123DecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_MPG123_HXX +#define MPD_DECODER_MPG123_HXX + +extern const struct DecoderPlugin mpg123_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/OggCodec.cxx b/src/decoder/plugins/OggCodec.cxx new file mode 100644 index 000000000..c7f39586e --- /dev/null +++ b/src/decoder/plugins/OggCodec.cxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 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 functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#include "config.h" +#include "OggCodec.hxx" +#include "../DecoderAPI.hxx" + +#include <string.h> + +enum ogg_codec +ogg_codec_detect(Decoder *decoder, InputStream &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 = decoder_read(decoder, is, buf, sizeof(buf)); + if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) + 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 OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; + + return OGG_CODEC_VORBIS; +} diff --git a/src/decoder/plugins/OggCodec.hxx b/src/decoder/plugins/OggCodec.hxx new file mode 100644 index 000000000..3b096561c --- /dev/null +++ b/src/decoder/plugins/OggCodec.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2014 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 functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#ifndef MPD_OGG_CODEC_HXX +#define MPD_OGG_CODEC_HXX + +struct Decoder; +class InputStream; + +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; + +enum ogg_codec +ogg_codec_detect(Decoder *decoder, InputStream &is); + +#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/plugins/OggFind.cxx b/src/decoder/plugins/OggFind.cxx new file mode 100644 index 000000000..978e1d7cf --- /dev/null +++ b/src/decoder/plugins/OggFind.cxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2014 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 "OggFind.hxx" +#include "OggSyncState.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" + +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) +{ + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r == 0) { + if (!oy.ExpectPageIn(os)) + return false; + + continue; + } else if (r > 0 && packet.e_o_s) + return true; + } +} + +bool +OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, + offset_type offset) +{ + oy.Reset(); + + /* reset the stream to clear any previous partial packet + data */ + ogg_stream_reset(&os); + + return is.LockSeek(offset, IgnoreError()) && + oy.ExpectPageSeekIn(os); +} + +bool +OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + InputStream &is) +{ + if (!is.KnownSize()) + return false; + + if (is.GetRest() < 65536) + return OggFindEOS(oy, os, packet); + + if (!is.CheapSeeking()) + return false; + + return OggSeekPageAtOffset(oy, os, is, is.GetSize() - 65536) && + OggFindEOS(oy, os, packet); +} diff --git a/src/decoder/plugins/OggFind.hxx b/src/decoder/plugins/OggFind.hxx new file mode 100644 index 000000000..2aa4f6c06 --- /dev/null +++ b/src/decoder/plugins/OggFind.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2014 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_FIND_HXX +#define MPD_OGG_FIND_HXX + +#include "check.h" +#include "input/Offset.hxx" + +#include <ogg/ogg.h> + +class OggSyncState; +class InputStream; + +/** + * Skip all pages/packets until an end-of-stream (EOS) packet for the + * specified stream is found. + * + * @return true if the EOS packet was found + */ +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); + +/** + * Seek the #InputStream and find the next Ogg page. + */ +bool +OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, + offset_type offset); + +/** + * Try to find the end-of-stream (EOS) packet. Seek to the end of the + * file if necessary. + * + * @return true if the EOS packet was found + */ +bool +OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + InputStream &is); + +#endif diff --git a/src/decoder/plugins/OggSyncState.hxx b/src/decoder/plugins/OggSyncState.hxx new file mode 100644 index 000000000..024902fff --- /dev/null +++ b/src/decoder/plugins/OggSyncState.hxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2014 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_SYNC_STATE_HXX +#define MPD_OGG_SYNC_STATE_HXX + +#include "check.h" +#include "OggUtil.hxx" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Wrapper for an ogg_sync_state. + */ +class OggSyncState { + ogg_sync_state oy; + + InputStream &is; + Decoder *const decoder; + +public: + OggSyncState(InputStream &_is, Decoder *const _decoder=nullptr) + :is(_is), decoder(_decoder) { + ogg_sync_init(&oy); + } + + ~OggSyncState() { + ogg_sync_clear(&oy); + } + + void Reset() { + ogg_sync_reset(&oy); + } + + bool Feed(size_t size) { + return OggFeed(oy, decoder, is, size); + } + + bool ExpectPage(ogg_page &page) { + return OggExpectPage(oy, page, decoder, is); + } + + bool ExpectFirstPage(ogg_stream_state &os) { + return OggExpectFirstPage(oy, os, decoder, is); + } + + bool ExpectPageIn(ogg_stream_state &os) { + return OggExpectPageIn(oy, os, decoder, is); + } + + bool ExpectPageSeek(ogg_page &page) { + return OggExpectPageSeek(oy, page, decoder, is); + } + + bool ExpectPageSeekIn(ogg_stream_state &os) { + return OggExpectPageSeekIn(oy, os, decoder, is); + } +}; + +#endif diff --git a/src/decoder/plugins/OggUtil.cxx b/src/decoder/plugins/OggUtil.cxx new file mode 100644 index 000000000..6341b0008 --- /dev/null +++ b/src/decoder/plugins/OggUtil.cxx @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2003-2014 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" +#include "../DecoderAPI.hxx" + +bool +OggFeed(ogg_sync_state &oy, Decoder *decoder, + InputStream &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, + Decoder *decoder, InputStream &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; + } +} + +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + Decoder *decoder, InputStream &input_stream) +{ + size_t remaining_skipped = 32768; + + while (true) { + int r = ogg_sync_pageseek(&oy, &page); + if (r > 0) + return true; + + if (r < 0) { + /* skipped -r bytes */ + size_t nbytes = -r; + if (nbytes > remaining_skipped) + /* still no ogg page - we lost our + patience, abort */ + return false; + + remaining_skipped -= nbytes; + continue; + } + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is) +{ + ogg_page page; + if (!OggExpectPageSeek(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} diff --git a/src/decoder/plugins/OggUtil.hxx b/src/decoder/plugins/OggUtil.hxx new file mode 100644 index 000000000..94c380ef4 --- /dev/null +++ b/src/decoder/plugins/OggUtil.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2014 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> + +class InputStream; +struct Decoder; + +/** + * Feed data from the #InputStream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &is, + 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, + Decoder *decoder, InputStream &is); + +/** + * Combines OggExpectPage(), ogg_stream_init() and + * ogg_stream_pagein(). + * + * @return true if the stream was initialized and the first page was + * delivered to it + */ +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is); + +/** + * Combines OggExpectPage() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is); + +/** + * Like OggExpectPage(), but allow skipping garbage (after seeking). + */ +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + Decoder *decoder, InputStream &is); + +/** + * Combines OggExpectPageSeek() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is); + +#endif diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..e14827e38 --- /dev/null +++ b/src/decoder/plugins/OpusDecoderPlugin.cxx @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2003-2014 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 "OpusDomain.hxx" +#include "OpusHead.hxx" +#include "OpusTags.hxx" +#include "OggFind.hxx" +#include "OggSyncState.hxx" +#include "../DecoderAPI.hxx" +#include "OggCodec.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <string.h> +#include <stdio.h> + +static constexpr opus_int32 opus_sample_rate = 48000; + +/** + * Allocate an output buffer for 16 bit PCM samples big enough to hold + * a quarter second, larger than 120ms required by libopus. + */ +static constexpr unsigned opus_output_buffer_frames = opus_sample_rate / 4; + +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(gcc_unused const config_param ¶m) +{ + LogDebug(opus_domain, opus_get_version_string()); + + return true; +} + +class MPDOpusDecoder { + Decoder &decoder; + InputStream &input_stream; + + ogg_stream_state os; + + OpusDecoder *opus_decoder; + opus_int16 *output_buffer; + + /** + * If non-zero, then a previous Opus stream has been found + * already with this number of channels. If opus_decoder is + * nullptr, then its end-of-stream packet has been found + * already. + */ + unsigned previous_channels; + + bool os_initialized; + + int opus_serialno; + + ogg_int64_t eos_granulepos; + + size_t frame_size; + +public: + MPDOpusDecoder(Decoder &_decoder, + InputStream &_input_stream) + :decoder(_decoder), input_stream(_input_stream), + opus_decoder(nullptr), + output_buffer(nullptr), + previous_channels(0), + os_initialized(false) {} + ~MPDOpusDecoder(); + + bool ReadFirstPage(OggSyncState &oy); + bool ReadNextPage(OggSyncState &oy); + + DecoderCommand HandlePackets(); + DecoderCommand HandlePacket(const ogg_packet &packet); + DecoderCommand HandleBOS(const ogg_packet &packet); + DecoderCommand HandleEOS(); + DecoderCommand HandleTags(const ogg_packet &packet); + DecoderCommand HandleAudio(const ogg_packet &packet); + + bool Seek(OggSyncState &oy, uint64_t where_frame); +}; + +MPDOpusDecoder::~MPDOpusDecoder() +{ + delete[] output_buffer; + + if (opus_decoder != nullptr) + opus_decoder_destroy(opus_decoder); + + if (os_initialized) + ogg_stream_clear(&os); +} + +inline bool +MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) +{ + assert(!os_initialized); + + if (!oy.ExpectFirstPage(os)) + return false; + + os_initialized = true; + return true; +} + +inline bool +MPDOpusDecoder::ReadNextPage(OggSyncState &oy) +{ + assert(os_initialized); + + ogg_page page; + if (!oy.ExpectPage(page)) + return false; + + const auto page_serialno = ogg_page_serialno(&page); + if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + return true; +} + +inline DecoderCommand +MPDOpusDecoder::HandlePackets() +{ + ogg_packet packet; + while (ogg_stream_packetout(&os, &packet) == 1) { + auto cmd = HandlePacket(packet); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + +inline DecoderCommand +MPDOpusDecoder::HandlePacket(const ogg_packet &packet) +{ + if (packet.e_o_s) + return HandleEOS(); + + if (packet.b_o_s) + return HandleBOS(packet); + else if (opus_decoder == nullptr) { + LogDebug(opus_domain, "BOS packet expected"); + return DecoderCommand::STOP; + } + + if (IsOpusTags(packet)) + return HandleTags(packet); + + return HandleAudio(packet); +} + +/** + * Load the end-of-stream packet and restore the previous file + * position. + */ +static bool +LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, + ogg_packet &packet) +{ + if (!is.CheapSeeking()) + /* we do this for local files only, because seeking + around remote files is expensive and not worth the + troubl */ + return false; + + const auto old_offset = is.GetOffset(); + + /* create temporary Ogg objects for seeking and parsing the + EOS packet */ + OggSyncState oy(is, decoder); + ogg_stream_state os; + ogg_stream_init(&os, serialno); + + bool result = OggSeekFindEOS(oy, os, packet, is); + ogg_stream_clear(&os); + + /* restore the previous file position */ + is.LockSeek(old_offset, IgnoreError()); + + return result; +} + +/** + * Load the end-of-stream granulepos and restore the previous file + * position. + * + * @return -1 on error + */ +gcc_pure +static ogg_int64_t +LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno) +{ + ogg_packet packet; + if (!LoadEOSPacket(is, decoder, serialno, packet)) + return -1; + + return packet.granulepos; +} + +inline DecoderCommand +MPDOpusDecoder::HandleBOS(const ogg_packet &packet) +{ + assert(packet.b_o_s); + + if (opus_decoder != nullptr || !IsOpusHead(packet)) { + LogDebug(opus_domain, "BOS packet must be OpusHead"); + return DecoderCommand::STOP; + } + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) { + LogDebug(opus_domain, "Malformed BOS packet"); + return DecoderCommand::STOP; + } + + assert(opus_decoder == nullptr); + assert((previous_channels == 0) == (output_buffer == nullptr)); + + if (previous_channels != 0 && channels != previous_channels) { + FormatWarning(opus_domain, + "Next stream has different channels (%u -> %u)", + previous_channels, channels); + return DecoderCommand::STOP; + } + + opus_serialno = os.serialno; + + /* 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) { + FormatError(opus_domain, "libopus error: %s", + opus_strerror(opus_error)); + return DecoderCommand::STOP; + } + + if (previous_channels != 0) { + /* decoder was already initialized by the previous + stream; skip the rest of this method */ + LogDebug(opus_domain, "Found another stream"); + return decoder_get_command(decoder); + } + + eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, + opus_serialno); + const auto duration = eos_granulepos >= 0 + ? SignedSongTime::FromScale<uint64_t>(eos_granulepos, + opus_sample_rate) + : SignedSongTime::Negative(); + + previous_channels = channels; + const AudioFormat audio_format(opus_sample_rate, + SampleFormat::S16, channels); + decoder_initialized(decoder, audio_format, + eos_granulepos > 0, duration); + frame_size = audio_format.GetFrameSize(); + + output_buffer = new opus_int16[opus_output_buffer_frames + * audio_format.channels]; + + return decoder_get_command(decoder); +} + +inline DecoderCommand +MPDOpusDecoder::HandleEOS() +{ + if (eos_granulepos < 0 && previous_channels != 0) { + /* allow chaining of (unseekable) streams */ + assert(opus_decoder != nullptr); + assert(output_buffer != nullptr); + + opus_decoder_destroy(opus_decoder); + opus_decoder = nullptr; + + return decoder_get_command(decoder); + } + + return DecoderCommand::STOP; +} + +inline DecoderCommand +MPDOpusDecoder::HandleTags(const ogg_packet &packet) +{ + ReplayGainInfo rgi; + rgi.Clear(); + + TagBuilder tag_builder; + + DecoderCommand cmd; + if (ScanOpusTags(packet.packet, packet.bytes, + &rgi, + &add_tag_handler, &tag_builder) && + !tag_builder.IsEmpty()) { + decoder_replay_gain(decoder, &rgi); + + Tag tag = tag_builder.Commit(); + cmd = decoder_tag(decoder, input_stream, std::move(tag)); + } else + cmd = decoder_get_command(decoder); + + return cmd; +} + +inline DecoderCommand +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, opus_output_buffer_frames, + 0); + if (nframes < 0) { + FormatError(opus_domain, "libopus error: %s", + opus_strerror(nframes)); + return DecoderCommand::STOP; + } + + if (nframes > 0) { + const size_t nbytes = nframes * frame_size; + auto cmd = decoder_data(decoder, input_stream, + output_buffer, nbytes, + 0); + if (cmd != DecoderCommand::NONE) + return cmd; + + if (packet.granulepos > 0) + decoder_timestamp(decoder, + double(packet.granulepos) + / opus_sample_rate); + } + + return DecoderCommand::NONE; +} + +bool +MPDOpusDecoder::Seek(OggSyncState &oy, uint64_t where_frame) +{ + assert(eos_granulepos > 0); + assert(input_stream.IsSeekable()); + assert(input_stream.KnownSize()); + + const ogg_int64_t where_granulepos(where_frame); + + /* interpolate the file offset where we expect to find the + given granule position */ + /* TODO: implement binary search */ + offset_type offset(where_granulepos * input_stream.GetSize() + / eos_granulepos); + + return OggSeekPageAtOffset(oy, os, input_stream, offset); +} + +static void +mpd_opus_stream_decode(Decoder &decoder, + InputStream &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.LockRewind(IgnoreError()); + + MPDOpusDecoder d(decoder, input_stream); + OggSyncState oy(input_stream, &decoder); + + if (!d.ReadFirstPage(oy)) + return; + + while (true) { + auto cmd = d.HandlePackets(); + if (cmd == DecoderCommand::SEEK) { + if (d.Seek(oy, decoder_seek_where_frame(decoder))) + decoder_command_finished(decoder); + else + decoder_seek_error(decoder); + + continue; + } + + if (cmd != DecoderCommand::NONE) + break; + + if (!d.ReadNextPage(oy)) + break; + } +} + +static bool +mpd_opus_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + OggSyncState oy(is); + + ogg_stream_state os; + if (!oy.ExpectFirstPage(os)) + return false; + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + 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 (!oy.ExpectPageIn(os)) { + result = false; + break; + } + + 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, + nullptr, + handler, handler_ctx)) + result = false; + + break; + } + } + + if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) { + const auto duration = + SongTime::FromScale<uint64_t>(packet.granulepos, + opus_sample_rate); + tag_handler_invoke_duration(handler, handler_ctx, duration); + } + + ogg_stream_clear(&os); + + return result; +} + +static const char *const opus_suffixes[] = { + "opus", + "ogg", + "oga", + nullptr +}; + +static const char *const opus_mime_types[] = { + /* the official MIME type (RFC 5334) */ + "audio/ogg", + + /* deprecated (RFC 5334) */ + "application/ogg", + + /* deprecated; from an early draft */ + "audio/opus", + nullptr +}; + +const struct DecoderPlugin 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/plugins/OpusDecoderPlugin.h b/src/decoder/plugins/OpusDecoderPlugin.h new file mode 100644 index 000000000..260dab99a --- /dev/null +++ b/src/decoder/plugins/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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 DecoderPlugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/OpusDomain.cxx b/src/decoder/plugins/OpusDomain.cxx new file mode 100644 index 000000000..1efd64a48 --- /dev/null +++ b/src/decoder/plugins/OpusDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 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 "OpusDomain.hxx" +#include "util/Domain.hxx" + +const Domain opus_domain("opus"); diff --git a/src/decoder/plugins/OpusDomain.hxx b/src/decoder/plugins/OpusDomain.hxx new file mode 100644 index 000000000..fb19e0301 --- /dev/null +++ b/src/decoder/plugins/OpusDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 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_DOMAIN_HXX +#define MPD_OPUS_DOMAIN_HXX + +#include "check.h" + +extern const class Domain opus_domain; + +#endif diff --git a/src/decoder/plugins/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx new file mode 100644 index 000000000..bfa41d618 --- /dev/null +++ b/src/decoder/plugins/OpusHead.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 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> + +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/plugins/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx new file mode 100644 index 000000000..c478d8d90 --- /dev/null +++ b/src/decoder/plugins/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2014 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/plugins/OpusReader.hxx b/src/decoder/plugins/OpusReader.hxx new file mode 100644 index 000000000..c5b8e9107 --- /dev/null +++ b/src/decoder/plugins/OpusReader.hxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2014 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 <algorithm> + +#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; + + char *dest = new char[length + 1]; + *std::copy_n(src, length, dest) = 0; + return dest; + } +}; + +#endif diff --git a/src/decoder/plugins/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx new file mode 100644 index 000000000..aff5479c0 --- /dev/null +++ b/src/decoder/plugins/OpusTags.cxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2014 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.hxx" +#include "tag/TagHandler.hxx" +#include "tag/Tag.hxx" +#include "ReplayGainInfo.hxx" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +gcc_pure +static TagType +ParseOpusTagName(const char *name) +{ + TagType type = tag_name_parse_i(name); + if (type != TAG_NUM_OF_ITEM_TYPES) + return type; + + return tag_table_lookup_i(xiph_tags, name); +} + +static void +ScanOneOpusTag(const char *name, const char *value, + ReplayGainInfo *rgi, + const struct tag_handler *handler, void *ctx) +{ + if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) { + /* R128_TRACK_GAIN is a Q7.8 fixed point number in + dB */ + + char *endptr; + long l = strtol(value, &endptr, 10); + if (endptr > value && *endptr == 0) + rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.; + } + + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + TagType t = ParseOpusTagName(name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + ReplayGainInfo *rgi, + 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, rgi, handler, ctx); + } + + delete[] p; + } + + return true; +} diff --git a/src/decoder/plugins/OpusTags.hxx b/src/decoder/plugins/OpusTags.hxx new file mode 100644 index 000000000..be3ac3a8d --- /dev/null +++ b/src/decoder/plugins/OpusTags.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 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> + +struct ReplayGainInfo; + +bool +ScanOpusTags(const void *data, size_t size, + ReplayGainInfo *rgi, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/plugins/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx new file mode 100644 index 000000000..c07a7b9b1 --- /dev/null +++ b/src/decoder/plugins/PcmDecoderPlugin.cxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2014 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 "PcmDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "util/ByteReverse.hxx" +#include "Log.hxx" + +#include <string.h> + +static void +pcm_stream_decode(Decoder &decoder, InputStream &is) +{ + static constexpr AudioFormat audio_format = { + 44100, + SampleFormat::S16, + 2, + }; + + const char *const mime = is.GetMimeType(); + const bool reverse_endian = mime != nullptr && + strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; + + const auto frame_size = audio_format.GetFrameSize(); + + const auto total_time = is.KnownSize() + ? SignedSongTime::FromScale<uint64_t>(is.GetSize() / frame_size, + audio_format.sample_rate) + : SignedSongTime::Negative(); + + decoder_initialized(decoder, audio_format, + is.IsSeekable(), total_time); + + DecoderCommand cmd; + do { + char buffer[4096]; + + size_t nbytes = decoder_read(decoder, is, + buffer, sizeof(buffer)); + + if (nbytes == 0 && is.LockIsEOF()) + break; + + if (reverse_endian) + /* make sure we deliver samples in host byte order */ + reverse_bytes_16((uint16_t *)buffer, + (uint16_t *)buffer, + (uint16_t *)(buffer + nbytes)); + + cmd = nbytes > 0 + ? decoder_data(decoder, is, + buffer, nbytes, 0) + : decoder_get_command(decoder); + if (cmd == DecoderCommand::SEEK) { + uint64_t frame = decoder_seek_where_frame(decoder); + offset_type offset = frame * frame_size; + + Error error; + if (is.LockSeek(offset, error)) { + decoder_command_finished(decoder); + } else { + LogError(error); + decoder_seek_error(decoder); + } + + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); +} + +static const char *const pcm_mime_types[] = { + /* for streams obtained by the cdio_paranoia input plugin */ + "audio/x-mpd-cdda-pcm", + + /* same as above, but with reverse byte order */ + "audio/x-mpd-cdda-pcm-reverse", + + nullptr +}; + +const struct DecoderPlugin pcm_decoder_plugin = { + "pcm", + nullptr, + nullptr, + pcm_stream_decode, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + pcm_mime_types, +}; diff --git a/src/decoder/plugins/PcmDecoderPlugin.hxx b/src/decoder/plugins/PcmDecoderPlugin.hxx new file mode 100644 index 000000000..3582e5856 --- /dev/null +++ b/src/decoder/plugins/PcmDecoderPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/** \file + * + * Not really a decoder; this plugin forwards its input data "as-is". + * + * It was written only to support the "cdio_paranoia" input plugin, + * which does not need a decoder. + */ + +#ifndef MPD_DECODER_PCM_HXX +#define MPD_DECODER_PCM_HXX + +extern const struct DecoderPlugin pcm_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx new file mode 100644 index 000000000..8435f095f --- /dev/null +++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2003-2014 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 "SidplayDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/FormatString.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#include <sidplay/sidplay2.h> +#include <sidplay/builders/resid.h> +#include <sidplay/utils/SidTuneMod.h> + +#define SUBTUNE_PREFIX "tune_" + +static constexpr Domain sidplay_domain("sidplay"); + +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 = nullptr; + gchar *data; + gsize size; + + if (!g_file_get_contents(path, &data, &size, &error)) { + FormatError(sidplay_domain, + "unable to read songlengths file %s: %s", + path, error->message); + g_error_free(error); + return nullptr; + } + + /* 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) { + FormatError(sidplay_domain, + "unable to parse songlengths file %s: %s", + path, error->message); + g_error_free(error); + g_key_file_free(db); + return nullptr; + } + + g_key_file_set_list_separator(db, ' '); + return db; +} + +static bool +sidplay_init(const config_param ¶m) +{ + /* read the songlengths database file */ + songlength_file = param.GetBlockValue("songlength_database"); + if (songlength_file != nullptr) + songlength_database = sidplay_load_songlength_db(songlength_file); + + default_songlength = param.GetBlockValue("default_songlength", 0u); + + all_files_are_containers = + param.GetBlockValue("all_files_are_containers", true); + + path_with_subtune=g_pattern_spec_new( + "*/" SUBTUNE_PREFIX "???.sid"); + + filter_setting = param.GetBlockValue("filter", true); + + return true; +} + +static 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(Path path_fs) +{ + char *path_container = strdup(path_fs.c_str()); + + if(!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, nullptr)) + 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 unsigned +get_song_num(const char *path_fs) +{ + if(g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, nullptr)) { + char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + if(!sub) return 1; + + sub+=strlen("/" SUBTUNE_PREFIX); + int song_num=strtol(sub, nullptr, 10); + + if (errno == EINVAL) + return 1; + else + return song_num; + } else + return 1; +} + +/* get the song length in seconds */ +static SignedSongTime +get_song_length(Path path_fs) +{ + if (songlength_database == nullptr) + return SignedSongTime::Negative(); + + char *sid_file = get_container_name(path_fs); + SidTuneMod tune(sid_file); + free(sid_file); + if(!tune) { + LogWarning(sidplay_domain, + "failed to load file for calculating md5 sum"); + return SignedSongTime::Negative(); + } + char md5sum[SIDTUNE_MD5_LENGTH+1]; + tune.createMD5(md5sum); + + const unsigned song_num = get_song_num(path_fs.c_str()); + + gsize num_items; + gchar **values=g_key_file_get_string_list(songlength_database, + "Database", md5sum, &num_items, nullptr); + if(!values || song_num>num_items) { + g_strfreev(values); + return SignedSongTime::Negative(); + } + + int minutes=strtol(values[song_num-1], nullptr, 10); + if(errno==EINVAL) minutes=0; + + int seconds; + char *ptr=strchr(values[song_num-1], ':'); + if(ptr) { + seconds=strtol(ptr+1, nullptr, 10); + if(errno==EINVAL) seconds=0; + } else + seconds=0; + + g_strfreev(values); + + return SignedSongTime::FromS((minutes * 60) + seconds); +} + +static void +sidplay_file_decode(Decoder &decoder, Path path_fs) +{ + int channels; + + /* load the tune */ + + char *path_container=get_container_name(path_fs); + SidTune tune(path_container, nullptr, true); + free(path_container); + if (!tune) { + LogWarning(sidplay_domain, "failed to load file"); + return; + } + + const int song_num = get_song_num(path_fs.c_str()); + tune.selectSong(song_num); + + auto duration = get_song_length(path_fs); + if (duration.IsNegative() && default_songlength > 0) + duration = SongTime::FromS(default_songlength); + + /* initialize the player */ + + sidplay2 player; + int iret = player.load(&tune); + if (iret != 0) { + FormatWarning(sidplay_domain, + "sidplay2.load() failed: %s", player.error()); + return; + } + + /* initialize the builder */ + + ReSIDBuilder builder("ReSID"); + if (!builder) { + LogWarning(sidplay_domain, + "failed to initialize ReSIDBuilder"); + return; + } + + builder.create(player.info().maxsids); + if (!builder) { + LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); + return; + } + + builder.filter(filter_setting); + if (!builder) { + LogWarning(sidplay_domain, "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.precision = 16; + config.sidDefault = SID2_MOS6581; + config.sidEmulation = &builder; + config.sidModel = SID2_MODEL_CORRECT; + config.sidSamples = true; + config.sampleFormat = IsLittleEndian() + ? SID2_LITTLE_SIGNED + : SID2_BIG_SIGNED; + if (tune.isStereo()) { + config.playback = sid2_stereo; + channels = 2; + } else { + config.playback = sid2_mono; + channels = 1; + } + + iret = player.config(config); + if (iret != 0) { + FormatWarning(sidplay_domain, + "sidplay2.config() failed: %s", player.error()); + return; + } + + /* initialize the MPD decoder */ + + const AudioFormat audio_format(48000, SampleFormat::S16, channels); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, true, duration); + + /* .. and play */ + + const unsigned timebase = player.timebase(); + const unsigned end = duration.IsNegative() + ? 0u + : duration.ToScale<uint64_t>(timebase); + + DecoderCommand 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, nullptr, buffer, nbytes, 0); + + if (cmd == DecoderCommand::SEEK) { + unsigned data_time = player.time(); + unsigned target_time = + decoder_seek_time(decoder).ToScale(timebase); + + /* can't rewind so return to zero and seek forward */ + if(target_time<data_time) { + player.stop(); + data_time=0; + } + + /* ignore data until target time is reached */ + while(data_time<target_time) { + nbytes=player.play(buffer, sizeof(buffer)); + if(nbytes==0) + break; + data_time = player.time(); + } + + decoder_command_finished(decoder); + } + + if (end > 0 && player.time() >= end) + break; + + } while (cmd != DecoderCommand::STOP); +} + +static bool +sidplay_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + const int song_num = get_song_num(path_fs.c_str()); + char *path_container=get_container_name(path_fs); + + SidTune tune(path_container, nullptr, true); + free(path_container); + if (!tune) + return false; + + const SidTuneInfo &info = tune.getInfo(); + + /* title */ + const char *title; + if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr) + title=info.infoString[0]; + else + title=""; + + if(info.songs>1) { + char tag_title[1024]; + snprintf(tag_title, sizeof(tag_title), + "%s (%d/%d)", + title, song_num, info.songs); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); + } else + tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); + + /* artist */ + if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, + info.infoString[1]); + + /* track */ + char track[16]; + sprintf(track, "%d", song_num); + tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); + + /* time */ + const auto duration = get_song_length(path_fs); + if (!duration.IsNegative()) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime(duration)); + + return true; +} + +static char * +sidplay_container_scan(Path path_fs, const unsigned int tnum) +{ + SidTune tune(path_fs.c_str(), nullptr, true); + if (!tune) + return nullptr; + + const SidTuneInfo &info=tune.getInfo(); + + /* Don't treat sids containing a single tune + as containers */ + if(!all_files_are_containers && info.songs<2) + return nullptr; + + /* Construct container/tune path names, eg. + Delta.sid/tune_001.sid */ + if(tnum<=info.songs) { + return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum); + } else + return nullptr; +} + +static const char *const sidplay_suffixes[] = { + "sid", + "mus", + "str", + "prg", + "P00", + nullptr +}; + +extern const struct DecoderPlugin sidplay_decoder_plugin; +const struct DecoderPlugin sidplay_decoder_plugin = { + "sidplay", + sidplay_init, + sidplay_finish, + nullptr, /* stream_decode() */ + sidplay_file_decode, + sidplay_scan_file, + nullptr, /* stream_tag() */ + sidplay_container_scan, + sidplay_suffixes, + nullptr, /* mime_types */ +}; diff --git a/src/decoder/plugins/SidplayDecoderPlugin.hxx b/src/decoder/plugins/SidplayDecoderPlugin.hxx new file mode 100644 index 000000000..58786e646 --- /dev/null +++ b/src/decoder/plugins/SidplayDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_SIDPLAY_HXX +#define MPD_DECODER_SIDPLAY_HXX + +extern const struct DecoderPlugin sidplay_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/SndfileDecoderPlugin.cxx b/src/decoder/plugins/SndfileDecoderPlugin.cxx new file mode 100644 index 000000000..5518c70be --- /dev/null +++ b/src/decoder/plugins/SndfileDecoderPlugin.cxx @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2003-2014 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 "SndfileDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <sndfile.h> + +static constexpr Domain sndfile_domain("sndfile"); + +static bool +sndfile_init(gcc_unused const config_param ¶m) +{ + LogDebug(sndfile_domain, sf_version_string()); + return true; +} + +struct SndfileInputStream { + Decoder *const decoder; + InputStream &is; + + size_t Read(void *buffer, size_t size) { + /* libsndfile chokes on partial reads; therefore + always force full reads */ + return decoder_read_full(decoder, is, buffer, size) + ? size + : 0; + } +}; + +static sf_count_t +sndfile_vio_get_filelen(void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + const InputStream &is = sis.is; + + if (!is.KnownSize()) + return -1; + + return is.GetSize(); +} + +static sf_count_t +sndfile_vio_seek(sf_count_t _offset, int whence, void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + InputStream &is = sis.is; + + offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + Error error; + if (!is.LockSeek(offset, error)) { + LogError(error, "Seek failed"); + return -1; + } + + return is.GetOffset(); +} + +static sf_count_t +sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + + return sis.Read(ptr, count); +} + +static sf_count_t +sndfile_vio_write(gcc_unused const void *ptr, + gcc_unused sf_count_t count, + gcc_unused void *user_data) +{ + /* no writing! */ + return -1; +} + +static sf_count_t +sndfile_vio_tell(void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + const InputStream &is = sis.is; + + return is.GetOffset(); +} + +/** + * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a + * libsndfile stream. + */ +static SF_VIRTUAL_IO vio = { + sndfile_vio_get_filelen, + sndfile_vio_seek, + sndfile_vio_read, + sndfile_vio_write, + sndfile_vio_tell, +}; + +/** + * Converts a frame number to a timestamp (in seconds). + */ +static constexpr SongTime +sndfile_duration(const SF_INFO &info) +{ + return SongTime::FromScale<uint64_t>(info.frames, info.samplerate); +} + +gcc_pure +static SampleFormat +sndfile_sample_format(const SF_INFO &info) +{ + switch (info.format & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_S8: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_16: + return SampleFormat::S16; + + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + return SampleFormat::FLOAT; + + default: + return SampleFormat::S32; + } +} + +static sf_count_t +sndfile_read_frames(SNDFILE *sf, SampleFormat format, + void *buffer, sf_count_t n_frames) +{ + switch (format) { + case SampleFormat::S16: + return sf_readf_short(sf, (short *)buffer, n_frames); + + case SampleFormat::S32: + return sf_readf_int(sf, (int *)buffer, n_frames); + + case SampleFormat::FLOAT: + return sf_readf_float(sf, (float *)buffer, n_frames); + + default: + assert(false); + gcc_unreachable(); + } +} + +static void +sndfile_stream_decode(Decoder &decoder, InputStream &is) +{ + SF_INFO info; + + info.format = 0; + + SndfileInputStream sis{&decoder, is}; + SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); + if (sf == nullptr) { + FormatWarning(sndfile_domain, "sf_open_virtual() failed: %s", + sf_strerror(nullptr)); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, info.samplerate, + sndfile_sample_format(info), + info.channels, error)) { + LogError(error); + return; + } + + decoder_initialized(decoder, audio_format, info.seekable, + sndfile_duration(info)); + + char buffer[16384]; + + const size_t frame_size = audio_format.GetFrameSize(); + const sf_count_t read_frames = sizeof(buffer) / frame_size; + + DecoderCommand cmd; + do { + sf_count_t num_frames = + sndfile_read_frames(sf, + audio_format.format, + buffer, read_frames); + if (num_frames <= 0) + break; + + cmd = decoder_data(decoder, is, + buffer, num_frames * frame_size, + 0); + if (cmd == DecoderCommand::SEEK) { + sf_count_t c = decoder_seek_where_frame(decoder); + c = sf_seek(sf, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + sf_close(sf); +} + +static void +sndfile_handle_tag(SNDFILE *sf, int str, TagType tag, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value = sf_get_string(sf, str); + if (value != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, tag, value); +} + +static constexpr struct { + int8_t str; + TagType tag; +} sndfile_tags[] = { + { SF_STR_TITLE, TAG_TITLE }, + { SF_STR_ARTIST, TAG_ARTIST }, + { SF_STR_COMMENT, TAG_COMMENT }, + { SF_STR_DATE, TAG_DATE }, + { SF_STR_ALBUM, TAG_ALBUM }, + { SF_STR_TRACKNUMBER, TAG_TRACK }, + { SF_STR_GENRE, TAG_GENRE }, +}; + +static bool +sndfile_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + SF_INFO info; + + info.format = 0; + + SndfileInputStream sis{nullptr, is}; + SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); + if (sf == nullptr) + return false; + + if (!audio_valid_sample_rate(info.samplerate)) { + sf_close(sf); + FormatWarning(sndfile_domain, + "Invalid sample rate in %s", is.GetURI()); + return false; + } + + tag_handler_invoke_duration(handler, handler_ctx, + sndfile_duration(info)); + + for (auto i : sndfile_tags) + sndfile_handle_tag(sf, i.str, i.tag, handler, handler_ctx); + + sf_close(sf); + + return true; +} + +static const char *const sndfile_suffixes[] = { + "wav", "aiff", "aif", /* Microsoft / SGI / Apple */ + "au", "snd", /* Sun / DEC / NeXT */ + "paf", /* Paris Audio File */ + "iff", "svx", /* Commodore Amiga IFF / SVX */ + "sf", /* IRCAM */ + "voc", /* Creative */ + "w64", /* Soundforge */ + "pvf", /* Portable Voice Format */ + "xi", /* Fasttracker */ + "htk", /* HMM Tool Kit */ + "caf", /* Apple */ + "sd2", /* Sound Designer II */ + + /* libsndfile also supports FLAC and Ogg Vorbis, but only by + linking with libFLAC and libvorbis - we can do better, we + have native plugins for these libraries */ + + nullptr +}; + +static const char *const sndfile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + + /* what are the MIME types of the other supported formats? */ + + nullptr +}; + +const struct DecoderPlugin sndfile_decoder_plugin = { + "sndfile", + sndfile_init, + nullptr, + sndfile_stream_decode, + nullptr, + nullptr, + sndfile_scan_stream, + nullptr, + sndfile_suffixes, + sndfile_mime_types, +}; diff --git a/src/decoder/plugins/SndfileDecoderPlugin.hxx b/src/decoder/plugins/SndfileDecoderPlugin.hxx new file mode 100644 index 000000000..d56acdd5a --- /dev/null +++ b/src/decoder/plugins/SndfileDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_SNDFILE_HXX +#define MPD_DECODER_SNDFILE_HXX + +extern const struct DecoderPlugin sndfile_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx new file mode 100644 index 000000000..062f46acf --- /dev/null +++ b/src/decoder/plugins/VorbisComments.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2014 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 "VorbisComments.hxx" +#include "XiphTags.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/VorbisComment.hxx" +#include "tag/ReplayGain.hxx" +#include "ReplayGainInfo.hxx" +#include "util/ASCII.hxx" +#include "util/SplitString.hxx" + +#include <stddef.h> +#include <stdlib.h> + +bool +vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) +{ + rgi.Clear(); + + bool found = false; + + while (*comments) { + if (ParseReplayGainVorbis(rgi, *comments)) + found = true; + + comments++; + } + + return found; +} + +/** + * 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(const char *comment, + const char *name, TagType tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + return true; + } + + return false; +} + +static void +vorbis_scan_comment(const char *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != nullptr) { + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) + tag_handler_invoke_pair(handler, handler_ctx, + split.GetFirst(), + split.GetSecond()); + } + + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (vorbis_copy_comment(comment, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (vorbis_copy_comment(comment, + tag_item_names[i], TagType(i), + handler, handler_ctx)) + return; +} + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx) +{ + while (*comments) + vorbis_scan_comment(*comments++, + handler, handler_ctx); + +} + +Tag * +vorbis_comments_to_tag(char **comments) +{ + TagBuilder tag_builder; + vorbis_comments_scan(comments, &add_tag_handler, &tag_builder); + return tag_builder.IsEmpty() + ? nullptr + : tag_builder.CommitNew(); +} diff --git a/src/decoder/plugins/VorbisComments.hxx b/src/decoder/plugins/VorbisComments.hxx new file mode 100644 index 000000000..893c89277 --- /dev/null +++ b/src/decoder/plugins/VorbisComments.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 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_VORBIS_COMMENTS_HXX +#define MPD_VORBIS_COMMENTS_HXX + +#include "check.h" + +struct ReplayGainInfo; +struct tag_handler; +struct Tag; + +bool +vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments); + +void +vorbis_comments_scan(char **comments, + const tag_handler *handler, void *handler_ctx); + +Tag * +vorbis_comments_to_tag(char **comments); + +#endif diff --git a/src/decoder/plugins/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx new file mode 100644 index 000000000..e0d3d1374 --- /dev/null +++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2003-2014 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 "VorbisDecoderPlugin.h" +#include "VorbisComments.hxx" +#include "VorbisDomain.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "OggCodec.hxx" +#include "util/Error.hxx" +#include "util/Macros.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "Log.hxx" + +#ifndef HAVE_TREMOR +#define OV_EXCLUDE_STATIC_CALLBACKS +#include <vorbis/vorbisfile.h> +#else +#include <tremor/ivorbisfile.h> +/* 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 <errno.h> + +struct VorbisInputStream { + Decoder *const decoder; + + InputStream &input_stream; + bool seekable; + + VorbisInputStream(Decoder *_decoder, InputStream &_is) + :decoder(_decoder), input_stream(_is), + seekable(input_stream.CheapSeeking()) {} +}; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) +{ + VorbisInputStream *vis = (VorbisInputStream *)data; + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); + + errno = 0; + + return ret / size; +} + +static int ogg_seek_cb(void *data, ogg_int64_t _offset, int whence) +{ + VorbisInputStream *vis = (VorbisInputStream *)data; + InputStream &is = vis->input_stream; + + if (!vis->seekable || + (vis->decoder != nullptr && + decoder_get_command(*vis->decoder) == DecoderCommand::STOP)) + return -1; + + offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + return is.LockSeek(offset, IgnoreError()) + ? 0 : -1; +} + +/* TODO: check Ogg libraries API and see if we can just not have this func */ +static int ogg_close_cb(gcc_unused void *data) +{ + return 0; +} + +static long ogg_tell_cb(void *data) +{ + VorbisInputStream *vis = (VorbisInputStream *)data; + + return (long)vis->input_stream.GetOffset(); +} + +static const ov_callbacks vorbis_is_callbacks = { + ogg_read_cb, + ogg_seek_cb, + ogg_close_cb, + ogg_tell_cb, +}; + +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 bool +vorbis_is_open(VorbisInputStream *vis, OggVorbis_File *vf) +{ + int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); + if (ret < 0) { + if (vis->decoder == nullptr || + decoder_get_command(*vis->decoder) == DecoderCommand::NONE) + FormatWarning(vorbis_domain, + "Failed to open Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return false; + } + + return true; +} + +static void +vorbis_send_comments(Decoder &decoder, InputStream &is, + char **comments) +{ + Tag *tag = vorbis_comments_to_tag(comments); + if (!tag) + return; + + decoder_tag(decoder, is, std::move(*tag)); + delete 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 *gcc_restrict d = dest; + for (const float *gcc_restrict s = *src, *s_end = s + nframes; + s != s_end; ++s, d += channels) + *d = *s; + } +} +#endif + +/* public */ + +static bool +vorbis_init(gcc_unused const config_param ¶m) +{ +#ifndef HAVE_TREMOR + LogDebug(vorbis_domain, vorbis_version_string()); +#endif + return true; +} + +gcc_pure +static SignedSongTime +vorbis_duration(OggVorbis_File &vf) +{ + auto total = ov_time_total(&vf, -1); + return total >= 0 + ? SignedSongTime::FromS(total) + : SignedSongTime::Negative(); +} + +static void +vorbis_stream_decode(Decoder &decoder, + InputStream &input_stream) +{ + if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_VORBIS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream.LockRewind(IgnoreError()); + + VorbisInputStream vis(&decoder, input_stream); + OggVorbis_File vf; + if (!vorbis_is_open(&vis, &vf)) + return; + + const vorbis_info *vi = ov_info(&vf, -1); + if (vi == nullptr) { + LogWarning(vorbis_domain, "ov_info() has failed"); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, vi->rate, +#ifdef HAVE_TREMOR + SampleFormat::S16, +#else + SampleFormat::FLOAT, +#endif + vi->channels, error)) { + LogError(error); + return; + } + + decoder_initialized(decoder, audio_format, vis.seekable, + vorbis_duration(vf)); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + ARRAY_SIZE(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + + DecoderCommand cmd = decoder_get_command(decoder); + do { + if (cmd == DecoderCommand::SEEK) { + auto seek_where = decoder_seek_where_frame(decoder); + if (0 == ov_pcm_seek_page(&vf, seek_where)) { + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } + + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + IsBigEndian(), 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) { + vi = ov_info(&vf, -1); + if (vi == nullptr) { + LogWarning(vorbis_domain, + "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 */ + LogWarning(vorbis_domain, + "audio format change, stopping here"); + break; + } + + char **comments = ov_comment(&vf, -1)->user_comments; + vorbis_send_comments(decoder, input_stream, comments); + + ReplayGainInfo rgi; + if (vorbis_comments_to_replay_gain(rgi, comments)) + decoder_replay_gain(decoder, &rgi); + + prev_section = current_section; + } + + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; + + cmd = decoder_data(decoder, input_stream, + buffer, nbytes, + kbit_rate); + } while (cmd != DecoderCommand::STOP); + + ov_clear(&vf); +} + +static bool +vorbis_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + VorbisInputStream vis(nullptr, is); + OggVorbis_File vf; + + if (!vorbis_is_open(&vis, &vf)) + return false; + + const auto total = ov_time_total(&vf, -1); + if (total >= 0) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime::FromS(total)); + + vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, + handler, handler_ctx); + + ov_clear(&vf); + return true; +} + +static const char *const vorbis_suffixes[] = { + "ogg", "oga", nullptr +}; + +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", + nullptr +}; + +const struct DecoderPlugin vorbis_decoder_plugin = { + "vorbis", + vorbis_init, + nullptr, + vorbis_stream_decode, + nullptr, + nullptr, + vorbis_scan_stream, + nullptr, + vorbis_suffixes, + vorbis_mime_types +}; diff --git a/src/decoder/plugins/VorbisDecoderPlugin.h b/src/decoder/plugins/VorbisDecoderPlugin.h new file mode 100644 index 000000000..b54df2e97 --- /dev/null +++ b/src/decoder/plugins/VorbisDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_VORBIS_H +#define MPD_DECODER_VORBIS_H + +extern const struct DecoderPlugin vorbis_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/VorbisDomain.cxx b/src/decoder/plugins/VorbisDomain.cxx new file mode 100644 index 000000000..e3d880efa --- /dev/null +++ b/src/decoder/plugins/VorbisDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 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 "VorbisDomain.hxx" +#include "util/Domain.hxx" + +const Domain vorbis_domain("vorbis"); diff --git a/src/decoder/plugins/VorbisDomain.hxx b/src/decoder/plugins/VorbisDomain.hxx new file mode 100644 index 000000000..48715e328 --- /dev/null +++ b/src/decoder/plugins/VorbisDomain.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2014 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_VORBIS_DOMAIN_HXX +#define MPD_VORBIS_DOMAIN_HXX + +#include "check.h" + +class Domain; + +extern const Domain vorbis_domain; + +#endif diff --git a/src/decoder/plugins/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx new file mode 100644 index 000000000..67859bbd2 --- /dev/null +++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2003-2014 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 "WavpackDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "tag/ApeTag.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <wavpack/wavpack.h> +#include <glib.h> + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#define ERRORLEN 80 + +static constexpr Domain wavpack_domain("wavpack"); + +/** 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 = (int32_t *)buffer; + + switch (bytes_per_sample) { + case 1: { + int8_t *dst = (int8_t *)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. + */ + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* pass through and align 8-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + case 2: { + uint16_t *dst = (uint16_t *)buffer; + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* 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(gcc_unused int bytes_per_sample, void *buffer, + uint32_t count) +{ + float *p = (float *)buffer; + + while (count--) { + *p /= (1 << 23); + ++p; + } +} + +/** + * Choose a MPD sample format from libwavpacks' number of bits. + */ +static SampleFormat +wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) +{ + if (is_float) + return SampleFormat::FLOAT; + + switch (bytes_per_sample) { + case 1: + return SampleFormat::S8; + + case 2: + return SampleFormat::S16; + + case 3: + return SampleFormat::S24_P32; + + case 4: + return SampleFormat::S32; + + default: + return SampleFormat::UNDEFINED; + } +} + +/* + * This does the main decoding thing. + * Requires an already opened WavpackContext. + */ +static void +wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek) +{ + bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + SampleFormat sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), error)) { + LogError(error); + return; + } + + const format_samples_t format_samples = is_float + ? format_samples_float + : format_samples_int; + + const auto total_time = + SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc), + audio_format.sample_rate); + + const int bytes_per_sample = WavpackGetBytesPerSample(wpc); + const int output_sample_size = audio_format.GetFrameSize(); + + /* wavpack gives us all kind of samples in a 32-bit space */ + int32_t chunk[1024]; + const uint32_t samples_requested = ARRAY_SIZE(chunk) / + audio_format.channels; + + decoder_initialized(decoder, audio_format, can_seek, total_time); + + DecoderCommand cmd = decoder_get_command(decoder); + while (cmd != DecoderCommand::STOP) { + if (cmd == DecoderCommand::SEEK) { + if (can_seek) { + auto where = decoder_seek_where_frame(decoder); + + if (WavpackSeekSample(wpc, where)) { + decoder_command_finished(decoder); + } else { + decoder_seek_error(decoder); + } + } else { + decoder_seek_error(decoder); + } + } + + uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, + samples_requested); + if (samples_got == 0) + break; + + int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + + 0.5); + format_samples(bytes_per_sample, chunk, + samples_got * audio_format.channels); + + cmd = decoder_data(decoder, nullptr, chunk, + samples_got * output_sample_size, + bitrate); + } +} + +/** + * 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]; + if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0) + return false; + + *value_r = atof(buffer); + return true; +} + +static bool +wavpack_replaygain(ReplayGainInfo &rgi, + WavpackContext *wpc) +{ + rgi.Clear(); + + bool found = false; + found |= wavpack_tag_float(wpc, "replaygain_track_gain", + &rgi.tuples[REPLAY_GAIN_TRACK].gain); + found |= wavpack_tag_float(wpc, "replaygain_track_peak", + &rgi.tuples[REPLAY_GAIN_TRACK].peak); + found |= wavpack_tag_float(wpc, "replaygain_album_gain", + &rgi.tuples[REPLAY_GAIN_ALBUM].gain); + found |= wavpack_tag_float(wpc, "replaygain_album_peak", + &rgi.tuples[REPLAY_GAIN_ALBUM].peak); + + return found; +} + +static void +wavpack_scan_tag_item(WavpackContext *wpc, const char *name, + TagType type, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[1024]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_tag(handler, handler_ctx, type, buffer); + +} + +static void +wavpack_scan_pair(WavpackContext *wpc, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[8192]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_pair(handler, handler_ctx, name, buffer); +} + +/* + * Reads metainfo from the specified file. + */ +static bool +wavpack_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + char error[ERRORLEN]; + WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error, + OPEN_TAGS, 0); + if (wpc == nullptr) { + FormatError(wavpack_domain, + "failed to open WavPack file \"%s\": %s", + path_fs.c_str(), error); + return false; + } + + const auto duration = + SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc), + WavpackGetSampleRate(wpc)); + tag_handler_invoke_duration(handler, handler_ctx, duration); + + /* the WavPack format implies APEv2 tags, which means we can + reuse the mapping from tag_ape.c */ + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + const char *name = tag_item_names[i]; + if (name != nullptr) + wavpack_scan_tag_item(wpc, name, (TagType)i, + handler, handler_ctx); + } + + for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i) + wavpack_scan_tag_item(wpc, i->name, i->type, + handler, handler_ctx); + + if (handler->pair != nullptr) { + char name[64]; + + for (int i = 0, n = WavpackGetNumTagItems(wpc); + i < n; ++i) { + int len = WavpackGetTagItemIndexed(wpc, i, name, + sizeof(name)); + if (len <= 0 || (unsigned)len >= sizeof(name)) + continue; + + wavpack_scan_pair(wpc, name, handler, handler_ctx); + } + } + + WavpackCloseFile(wpc); + + return true; +} + +/* + * mpd input_stream <=> WavpackStreamReader wrapper callbacks + */ + +/* This struct is needed for per-stream last_byte storage. */ +struct WavpackInput { + Decoder &decoder; + InputStream &is; + /* Needed for push_back_byte() */ + int last_byte; + + constexpr WavpackInput(Decoder &_decoder, InputStream &_is) + :decoder(_decoder), is(_is), last_byte(EOF) {} + + int32_t ReadBytes(void *data, size_t bcount); +}; + +/** + * Little wrapper for struct WavpackInput to cast from void *. + */ +static WavpackInput * +wpin(void *id) +{ + assert(id); + return (WavpackInput *)id; +} + +static int32_t +wavpack_input_read_bytes(void *id, void *data, int32_t bcount) +{ + return wpin(id)->ReadBytes(data, bcount); +} + +int32_t +WavpackInput::ReadBytes(void *data, size_t bcount) +{ + uint8_t *buf = (uint8_t *)data; + int32_t i = 0; + + if (last_byte != EOF) { + *buf++ = last_byte; + 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(&decoder, 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) +{ + WavpackInput &wpi = *wpin(id); + + return wpi.is.GetOffset(); +} + +static int +wavpack_input_set_pos_abs(void *id, uint32_t pos) +{ + WavpackInput &wpi = *wpin(id); + + return wpi.is.LockSeek(pos, IgnoreError()) ? 0 : -1; +} + +static int +wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) +{ + WavpackInput &wpi = *wpin(id); + InputStream &is = wpi.is; + + offset_type offset = delta; + switch (mode) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + return is.LockSeek(offset, IgnoreError()) ? 0 : -1; +} + +static int +wavpack_input_push_back_byte(void *id, int c) +{ + WavpackInput &wpi = *wpin(id); + + if (wpi.last_byte == EOF) { + wpi.last_byte = c; + return c; + } else { + return EOF; + } +} + +static uint32_t +wavpack_input_get_length(void *id) +{ + WavpackInput &wpi = *wpin(id); + InputStream &is = wpi.is; + + if (!is.KnownSize()) + return 0; + + return is.GetSize(); +} + +static int +wavpack_input_can_seek(void *id) +{ + WavpackInput &wpi = *wpin(id); + InputStream &is = wpi.is; + + return is.IsSeekable(); +} + +static WavpackStreamReader mpd_is_reader = { + wavpack_input_read_bytes, + wavpack_input_get_pos, + wavpack_input_set_pos_abs, + wavpack_input_set_pos_rel, + wavpack_input_push_back_byte, + wavpack_input_get_length, + wavpack_input_can_seek, + nullptr /* no need to write edited tags */ +}; + +static WavpackInput * +wavpack_open_wvc(Decoder &decoder, const char *uri) +{ + /* + * As we use dc->utf8url, this function will be bad for + * single files. utf8url is not absolute file path :/ + */ + if (uri == nullptr) + return nullptr; + + char *wvc_url = g_strconcat(uri, "c", nullptr); + + InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError()); + g_free(wvc_url); + + if (is_wvc == nullptr) + return nullptr; + + return new WavpackInput(decoder, *is_wvc); +} + +/* + * Decodes a stream. + */ +static void +wavpack_streamdecode(Decoder &decoder, InputStream &is) +{ + int open_flags = OPEN_NORMALIZE; + bool can_seek = is.IsSeekable(); + + WavpackInput *wvc = wavpack_open_wvc(decoder, is.GetURI()); + if (wvc != nullptr) { + open_flags |= OPEN_WVC; + can_seek &= wvc->is.IsSeekable(); + } + + if (!can_seek) { + open_flags |= OPEN_STREAMING; + } + + WavpackInput isp(decoder, is); + + char error[ERRORLEN]; + WavpackContext *wpc = + WavpackOpenFileInputEx(&mpd_is_reader, &isp, wvc, + error, open_flags, 23); + + if (wpc == nullptr) { + FormatError(wavpack_domain, + "failed to open WavPack stream: %s", error); + return; + } + + wavpack_decode(decoder, wpc, can_seek); + + WavpackCloseFile(wpc); + + if (wvc != nullptr) { + delete &wvc->is; + delete wvc; + } +} + +/* + * Decodes a file. + */ +static void +wavpack_filedecode(Decoder &decoder, Path path_fs) +{ + char error[ERRORLEN]; + WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error, + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, + 23); + if (wpc == nullptr) { + FormatWarning(wavpack_domain, + "failed to open WavPack file \"%s\": %s", + path_fs.c_str(), error); + return; + } + + ReplayGainInfo rgi; + if (wavpack_replaygain(rgi, wpc)) + decoder_replay_gain(decoder, &rgi); + + wavpack_decode(decoder, wpc, true); + + WavpackCloseFile(wpc); +} + +static char const *const wavpack_suffixes[] = { + "wv", + nullptr +}; + +static char const *const wavpack_mime_types[] = { + "audio/x-wavpack", + nullptr +}; + +const struct DecoderPlugin wavpack_decoder_plugin = { + "wavpack", + nullptr, + nullptr, + wavpack_streamdecode, + wavpack_filedecode, + wavpack_scan_file, + nullptr, + nullptr, + wavpack_suffixes, + wavpack_mime_types +}; diff --git a/src/decoder/plugins/WavpackDecoderPlugin.hxx b/src/decoder/plugins/WavpackDecoderPlugin.hxx new file mode 100644 index 000000000..2e5f9bd42 --- /dev/null +++ b/src/decoder/plugins/WavpackDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_WAVPACK_HXX +#define MPD_DECODER_WAVPACK_HXX + +extern const struct DecoderPlugin wavpack_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx new file mode 100644 index 000000000..fc58f0977 --- /dev/null +++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2014 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 "WildmidiDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "fs/Path.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +extern "C" { +#include <wildmidi_lib.h> +} + +static constexpr Domain wildmidi_domain("wildmidi"); + +static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000; + +static bool +wildmidi_init(const config_param ¶m) +{ + Error error; + const AllocatedPath path = + param.GetBlockPath("config_file", + "/etc/timidity/timidity.cfg", + error); + if (path.IsNull()) + FatalError(error); + + if (!FileExists(path)) { + const auto utf8 = path.ToUTF8(); + FormatDebug(wildmidi_domain, + "configuration file does not exist: %s", + utf8.c_str()); + return false; + } + + return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0; +} + +static void +wildmidi_finish(void) +{ + WildMidi_Shutdown(); +} + +static void +wildmidi_file_decode(Decoder &decoder, Path path_fs) +{ + static constexpr AudioFormat audio_format = { + WILDMIDI_SAMPLE_RATE, + SampleFormat::S16, + 2, + }; + midi *wm; + const struct _WM_Info *info; + + wm = WildMidi_Open(path_fs.c_str()); + if (wm == nullptr) + return; + + info = WildMidi_GetInfo(wm); + if (info == nullptr) { + WildMidi_Close(wm); + return; + } + + const auto duration = + SongTime::FromScale<uint64_t>(info->approx_total_samples, + WILDMIDI_SAMPLE_RATE); + + decoder_initialized(decoder, audio_format, true, duration); + + DecoderCommand cmd; + do { + char buffer[4096]; + int len; + + info = WildMidi_GetInfo(wm); + if (info == nullptr) + break; + + len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); + if (len <= 0) + break; + + cmd = decoder_data(decoder, nullptr, buffer, len, 0); + + if (cmd == DecoderCommand::SEEK) { + unsigned long seek_where = + decoder_seek_where_frame(decoder); + + WildMidi_FastSeek(wm, &seek_where); + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + + } while (cmd == DecoderCommand::NONE); + + WildMidi_Close(wm); +} + +static bool +wildmidi_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + midi *wm = WildMidi_Open(path_fs.c_str()); + if (wm == nullptr) + return false; + + const struct _WM_Info *info = WildMidi_GetInfo(wm); + if (info == nullptr) { + WildMidi_Close(wm); + return false; + } + + const auto duration = + SongTime::FromScale<uint64_t>(info->approx_total_samples, + WILDMIDI_SAMPLE_RATE); + tag_handler_invoke_duration(handler, handler_ctx, duration); + + WildMidi_Close(wm); + + return true; +} + +static const char *const wildmidi_suffixes[] = { + "mid", + nullptr +}; + +const struct DecoderPlugin wildmidi_decoder_plugin = { + "wildmidi", + wildmidi_init, + wildmidi_finish, + nullptr, + wildmidi_file_decode, + wildmidi_scan_file, + nullptr, + nullptr, + wildmidi_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/WildmidiDecoderPlugin.hxx b/src/decoder/plugins/WildmidiDecoderPlugin.hxx new file mode 100644 index 000000000..fc87aab80 --- /dev/null +++ b/src/decoder/plugins/WildmidiDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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_WILDMIDI_HXX +#define MPD_DECODER_WILDMIDI_HXX + +extern const struct DecoderPlugin wildmidi_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx new file mode 100644 index 000000000..11a0bcd42 --- /dev/null +++ b/src/decoder/plugins/XiphTags.cxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* This File contains additional Tags for Xiph-Based Formats like Ogg-Vorbis, + * Flac and Opus which will be used in addition to the Tags in tag/TagNames.c + * see https://www.xiph.org/vorbis/doc/v-comment.html for further Info + */ +#include "config.h" +#include "XiphTags.hxx" + +const struct tag_table xiph_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { "description", TAG_COMMENT }, + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/decoder/plugins/XiphTags.hxx b/src/decoder/plugins/XiphTags.hxx new file mode 100644 index 000000000..48a27425f --- /dev/null +++ b/src/decoder/plugins/XiphTags.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 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_HXX +#define MPD_XIPH_TAGS_HXX + +#include "check.h" +#include "tag/TagTable.hxx" + +extern const struct tag_table xiph_tags[]; + +#endif |