aboutsummaryrefslogtreecommitdiffstats
path: root/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/decoder/AdPlugDecoderPlugin.cxx141
-rw-r--r--src/decoder/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/AudiofileDecoderPlugin.cxx283
-rw-r--r--src/decoder/AudiofileDecoderPlugin.hxx25
-rw-r--r--src/decoder/DecoderAPI.cxx641
-rw-r--r--src/decoder/DecoderAPI.hxx237
-rw-r--r--src/decoder/DecoderBuffer.cxx71
-rw-r--r--src/decoder/DecoderBuffer.hxx117
-rw-r--r--src/decoder/DecoderCommand.hxx32
-rw-r--r--src/decoder/DecoderControl.cxx139
-rw-r--r--src/decoder/DecoderControl.hxx395
-rw-r--r--src/decoder/DecoderError.cxx23
-rw-r--r--src/decoder/DecoderError.hxx25
-rw-r--r--src/decoder/DecoderInternal.cxx104
-rw-r--r--src/decoder/DecoderInternal.hxx126
-rw-r--r--src/decoder/DecoderList.cxx163
-rw-r--r--src/decoder/DecoderList.hxx89
-rw-r--r--src/decoder/DecoderPlugin.cxx42
-rw-r--r--src/decoder/DecoderPlugin.hxx186
-rw-r--r--src/decoder/DecoderPrint.cxx55
-rw-r--r--src/decoder/DecoderPrint.hxx28
-rw-r--r--src/decoder/DecoderThread.cxx509
-rw-r--r--src/decoder/DecoderThread.hxx28
-rw-r--r--src/decoder/DsdLib.cxx157
-rw-r--r--src/decoder/DsdLib.hxx78
-rw-r--r--src/decoder/DsdiffDecoderPlugin.cxx522
-rw-r--r--src/decoder/DsdiffDecoderPlugin.hxx25
-rw-r--r--src/decoder/DsfDecoderPlugin.cxx361
-rw-r--r--src/decoder/DsfDecoderPlugin.hxx25
-rw-r--r--src/decoder/FaadDecoderPlugin.cxx490
-rw-r--r--src/decoder/FaadDecoderPlugin.hxx25
-rw-r--r--src/decoder/FfmpegDecoderPlugin.cxx733
-rw-r--r--src/decoder/FfmpegDecoderPlugin.hxx25
-rw-r--r--src/decoder/FfmpegMetaData.cxx76
-rw-r--r--src/decoder/FfmpegMetaData.hxx38
-rw-r--r--src/decoder/FlacCommon.cxx194
-rw-r--r--src/decoder/FlacCommon.hxx94
-rw-r--r--src/decoder/FlacDecoderPlugin.cxx387
-rw-r--r--src/decoder/FlacDecoderPlugin.h26
-rw-r--r--src/decoder/FlacDomain.cxx24
-rw-r--r--src/decoder/FlacDomain.hxx27
-rw-r--r--src/decoder/FlacIOHandle.cxx114
-rw-r--r--src/decoder/FlacIOHandle.hxx45
-rw-r--r--src/decoder/FlacInput.cxx154
-rw-r--r--src/decoder/FlacInput.hxx75
-rw-r--r--src/decoder/FlacMetadata.cxx245
-rw-r--r--src/decoder/FlacMetadata.hxx141
-rw-r--r--src/decoder/FlacPcm.cxx110
-rw-r--r--src/decoder/FlacPcm.hxx33
-rw-r--r--src/decoder/FluidsynthDecoderPlugin.cxx224
-rw-r--r--src/decoder/FluidsynthDecoderPlugin.hxx25
-rw-r--r--src/decoder/GmeDecoderPlugin.cxx295
-rw-r--r--src/decoder/GmeDecoderPlugin.hxx25
-rw-r--r--src/decoder/MadDecoderPlugin.cxx1155
-rw-r--r--src/decoder/MadDecoderPlugin.hxx25
-rw-r--r--src/decoder/MikmodDecoderPlugin.cxx248
-rw-r--r--src/decoder/MikmodDecoderPlugin.hxx25
-rw-r--r--src/decoder/ModplugDecoderPlugin.cxx215
-rw-r--r--src/decoder/ModplugDecoderPlugin.hxx25
-rw-r--r--src/decoder/MpcdecDecoderPlugin.cxx280
-rw-r--r--src/decoder/MpcdecDecoderPlugin.hxx25
-rw-r--r--src/decoder/Mpg123DecoderPlugin.cxx256
-rw-r--r--src/decoder/Mpg123DecoderPlugin.hxx25
-rw-r--r--src/decoder/OggCodec.cxx50
-rw-r--r--src/decoder/OggCodec.hxx39
-rw-r--r--src/decoder/OggFind.cxx68
-rw-r--r--src/decoder/OggFind.hxx57
-rw-r--r--src/decoder/OggSyncState.hxx78
-rw-r--r--src/decoder/OggUtil.cxx118
-rw-r--r--src/decoder/OggUtil.hxx87
-rw-r--r--src/decoder/OpusDecoderPlugin.cxx489
-rw-r--r--src/decoder/OpusDecoderPlugin.h25
-rw-r--r--src/decoder/OpusDomain.cxx24
-rw-r--r--src/decoder/OpusDomain.hxx27
-rw-r--r--src/decoder/OpusHead.cxx44
-rw-r--r--src/decoder/OpusHead.hxx30
-rw-r--r--src/decoder/OpusReader.hxx101
-rw-r--r--src/decoder/OpusTags.cxx102
-rw-r--r--src/decoder/OpusTags.hxx34
-rw-r--r--src/decoder/PcmDecoderPlugin.cxx114
-rw-r--r--src/decoder/PcmDecoderPlugin.hxx33
-rw-r--r--src/decoder/SidplayDecoderPlugin.cxx435
-rw-r--r--src/decoder/SidplayDecoderPlugin.hxx25
-rw-r--r--src/decoder/SndfileDecoderPlugin.cxx273
-rw-r--r--src/decoder/SndfileDecoderPlugin.hxx25
-rw-r--r--src/decoder/VorbisComments.cxx149
-rw-r--r--src/decoder/VorbisComments.hxx39
-rw-r--r--src/decoder/VorbisDecoderPlugin.cxx352
-rw-r--r--src/decoder/VorbisDecoderPlugin.h25
-rw-r--r--src/decoder/VorbisDomain.cxx24
-rw-r--r--src/decoder/VorbisDomain.hxx27
-rw-r--r--src/decoder/WavpackDecoderPlugin.cxx571
-rw-r--r--src/decoder/WavpackDecoderPlugin.hxx25
-rw-r--r--src/decoder/WildmidiDecoderPlugin.cxx158
-rw-r--r--src/decoder/WildmidiDecoderPlugin.hxx25
-rw-r--r--src/decoder/XiphTags.cxx28
-rw-r--r--src/decoder/XiphTags.hxx28
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.cxx148
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.cxx297
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/DsdLib.cxx144
-rw-r--r--src/decoder/plugins/DsdLib.hxx86
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.cxx510
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.cxx383
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.cxx450
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx770
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FfmpegMetaData.cxx76
-rw-r--r--src/decoder/plugins/FfmpegMetaData.hxx38
-rw-r--r--src/decoder/plugins/FlacCommon.cxx193
-rw-r--r--src/decoder/plugins/FlacCommon.hxx93
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.cxx387
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.h26
-rw-r--r--src/decoder/plugins/FlacDomain.cxx24
-rw-r--r--src/decoder/plugins/FlacDomain.hxx27
-rw-r--r--src/decoder/plugins/FlacIOHandle.cxx134
-rw-r--r--src/decoder/plugins/FlacIOHandle.hxx45
-rw-r--r--src/decoder/plugins/FlacInput.cxx154
-rw-r--r--src/decoder/plugins/FlacInput.hxx75
-rw-r--r--src/decoder/plugins/FlacMetadata.cxx179
-rw-r--r--src/decoder/plugins/FlacMetadata.hxx131
-rw-r--r--src/decoder/plugins/FlacPcm.cxx110
-rw-r--r--src/decoder/plugins/FlacPcm.hxx33
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.cxx226
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.cxx298
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.cxx1104
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.cxx250
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.cxx217
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.cxx280
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.cxx341
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/OggCodec.cxx51
-rw-r--r--src/decoder/plugins/OggCodec.hxx40
-rw-r--r--src/decoder/plugins/OggFind.cxx70
-rw-r--r--src/decoder/plugins/OggFind.hxx57
-rw-r--r--src/decoder/plugins/OggSyncState.hxx78
-rw-r--r--src/decoder/plugins/OggUtil.cxx118
-rw-r--r--src/decoder/plugins/OggUtil.hxx87
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.cxx535
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.h25
-rw-r--r--src/decoder/plugins/OpusDomain.cxx24
-rw-r--r--src/decoder/plugins/OpusDomain.hxx27
-rw-r--r--src/decoder/plugins/OpusHead.cxx43
-rw-r--r--src/decoder/plugins/OpusHead.hxx30
-rw-r--r--src/decoder/plugins/OpusReader.hxx101
-rw-r--r--src/decoder/plugins/OpusTags.cxx102
-rw-r--r--src/decoder/plugins/OpusTags.hxx34
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.cxx111
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.hxx33
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.cxx439
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.cxx339
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/VorbisComments.cxx114
-rw-r--r--src/decoder/plugins/VorbisComments.hxx39
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.cxx389
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.h25
-rw-r--r--src/decoder/plugins/VorbisDomain.cxx24
-rw-r--r--src/decoder/plugins/VorbisDomain.hxx29
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.cxx587
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.cxx164
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/XiphTags.cxx33
-rw-r--r--src/decoder/plugins/XiphTags.hxx28
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 &param)
-{
- 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 = &empty;
+ 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 &param);
+
+ /**
+ * 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 &param) 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- 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 &param)
-{
- /* 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,
- &current_section);
-#else
- float **per_channel;
- long nframes = ov_read_float(&vf, &per_channel,
- frames_per_buffer,
- &current_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 &param)
-{
- 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ 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 &param)
+{
+ /* 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 &param)
+{
+ 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 &param)
+{
+#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,
+ &current_section);
+#else
+ float **per_channel;
+ long nframes = ov_read_float(&vf, &per_channel,
+ frames_per_buffer,
+ &current_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 &param)
+{
+ 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