diff options
author | Max Kellermann <max@musicpd.org> | 2018-02-12 23:17:19 +0100 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2018-02-13 00:00:00 +0100 |
commit | b2ec5d0f016f8f913b56c6f4dc4a277907e18db0 (patch) | |
tree | b57d22cce475807af78d6fb7b57b5f5a9706fb1e /src/decoder/plugins | |
parent | 0c300bd4b4dd1fdd54f0674c58ff2e6b646b1fbd (diff) |
decoder/HybridDSD: new decoder plugin for Hybrid DSD
Diffstat (limited to 'src/decoder/plugins')
-rw-r--r-- | src/decoder/plugins/HybridDsdDecoderPlugin.cxx | 240 | ||||
-rw-r--r-- | src/decoder/plugins/HybridDsdDecoderPlugin.hxx | 25 |
2 files changed, 265 insertions, 0 deletions
diff --git a/src/decoder/plugins/HybridDsdDecoderPlugin.cxx b/src/decoder/plugins/HybridDsdDecoderPlugin.cxx new file mode 100644 index 000000000..d26a5c721 --- /dev/null +++ b/src/decoder/plugins/HybridDsdDecoderPlugin.cxx @@ -0,0 +1,240 @@ +/* + * Copyright 2003-2017 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "HybridDsdDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "system/ByteOrder.hxx" +#include "util/Domain.hxx" +#include "util/WritableBuffer.hxx" +#include "util/StaticFifoBuffer.hxx" +#include "Log.hxx" + +#include <string.h> + +static constexpr Domain hybrid_dsd_domain("hybrid_dsd"); + +namespace { + +static bool +InitHybridDsdDecoder(const ConfigBlock &block) +{ + if (block.GetBlockParam("enabled") == nullptr) { + LogInfo(hybrid_dsd_domain, + "The Hybrid DSD decoder is disabled because it was not explicitly enabled"); + return false; + } + + return true; +} + +struct UnsupportedFile {}; + +struct Mp4ChunkHeader { + uint32_t size; + char type[4]; +}; + +void +ReadFull(DecoderClient &client, InputStream &input, + WritableBuffer<uint8_t> dest) +{ + while (!dest.empty()) { + size_t nbytes = client.Read(input, dest.data, dest.size); + if (nbytes == 0) + throw UnsupportedFile(); + + dest.skip_front(nbytes); + } +} + +void +ReadFull(DecoderClient &client, InputStream &input, WritableBuffer<void> dest) +{ + ReadFull(client, input, WritableBuffer<uint8_t>::FromVoid(dest)); +} + +template<typename T> +T +ReadFullT(DecoderClient &client, InputStream &input) +{ + T dest; + ReadFull(client, input, WritableBuffer<void>(&dest, sizeof(dest))); + return dest; +} + +Mp4ChunkHeader +ReadHeader(DecoderClient &client, InputStream &input) +{ + return ReadFullT<Mp4ChunkHeader>(client, input); +} + +uint32_t +ReadBE32(DecoderClient &client, InputStream &input) +{ + return FromBE32(ReadFullT<uint32_t>(client, input)); +} + +} /* anonymous namespace */ + +static std::pair<AudioFormat, offset_type> +FindHybridDsdData(DecoderClient &client, InputStream &input) +{ + auto audio_format = AudioFormat::Undefined(); + + while (true) { + auto header = ReadHeader(client, input); + const size_t header_size = FromBE32(header.size); + if (header_size < sizeof(header)) + throw UnsupportedFile(); + + size_t remaining = header_size - sizeof(header); + if (memcmp(header.type, "bphv", 4) == 0) { + /* ? */ + if (remaining != 4 || ReadBE32(client, input) != 1) + throw UnsupportedFile(); + remaining -= 4; + + audio_format.format = SampleFormat::DSD; + } else if (memcmp(header.type, "bphc", 4) == 0) { + /* channel count */ + if (remaining != 4) + throw UnsupportedFile(); + + auto channels = ReadBE32(client, input); + remaining -= 4; + + if (!audio_valid_channel_count(channels)) + throw UnsupportedFile(); + + audio_format.channels = channels; + } else if (memcmp(header.type, "bphr", 4) == 0) { + /* (bit) sample rate */ + + if (remaining != 4) + throw UnsupportedFile(); + + auto sample_rate = ReadBE32(client, input) / 8; + remaining -= 4; + + if (!audio_valid_sample_rate(sample_rate)) + throw UnsupportedFile(); + + audio_format.sample_rate = sample_rate; + } else if (memcmp(header.type, "bphf", 4) == 0) { + /* ? */ + if (remaining != 4 || ReadBE32(client, input) != 0) + throw UnsupportedFile(); + remaining -= 4; + } else if (memcmp(header.type, "bphd", 4) == 0) { + /* the actual DSD data */ + if (!audio_format.IsValid()) + throw UnsupportedFile(); + + return std::make_pair(audio_format, remaining); + } + + input.LockSkip(remaining); + } +} + +static void +HybridDsdDecode(DecoderClient &client, InputStream &input) +{ + if (!input.CheapSeeking()) + /* probe only if seeking is cheap, i.e. not for HTTP + streams */ + return; + + offset_type remaining_bytes; + size_t frame_size; + + try { + auto result = FindHybridDsdData(client, input); + auto duration = SignedSongTime::FromS(result.second / result.first.GetTimeToSize()); + client.Ready(result.first, + /* TODO: implement seeking */ false, + duration); + frame_size = result.first.GetFrameSize(); + remaining_bytes = result.second; + } catch (UnsupportedFile) { + return; + } + + StaticFifoBuffer<uint8_t, 16384> buffer; + + auto cmd = client.GetCommand(); + while (remaining_bytes > 0) { + switch (cmd) { + case DecoderCommand::NONE: + case DecoderCommand::START: + break; + + case DecoderCommand::STOP: + return; + + case DecoderCommand::SEEK: + // TODO: implement seeking + break; + } + + auto w = buffer.Write(); + if (!w.empty()) { + if (remaining_bytes < (1<<30ull) && + w.size > size_t(remaining_bytes)) + w.size = remaining_bytes; + + const size_t nbytes = client.Read(input, + w.data, w.size); + if (nbytes == 0) + return; + + remaining_bytes -= nbytes; + buffer.Append(nbytes); + } + + auto r = buffer.Read(); + auto n_frames = r.size / frame_size; + if (n_frames > 0) { + cmd = client.SubmitData(input, r.data, + n_frames * frame_size, + 0); + buffer.Consume(n_frames * frame_size); + } + } +} + +static const char *const hybrid_dsd_suffixes[] = { + "m4a", + nullptr +}; + +const struct DecoderPlugin hybrid_dsd_decoder_plugin = { + "hybrid_dsd", + InitHybridDsdDecoder, + nullptr, + HybridDsdDecode, + nullptr, + nullptr, + nullptr, + nullptr, + hybrid_dsd_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/HybridDsdDecoderPlugin.hxx b/src/decoder/plugins/HybridDsdDecoderPlugin.hxx new file mode 100644 index 000000000..3c4cf9f70 --- /dev/null +++ b/src/decoder/plugins/HybridDsdDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright 2003-2018 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HYBRID_DSD_HXX +#define MPD_DECODER_HYBRID_DSD_HXX + +extern const struct DecoderPlugin hybrid_dsd_decoder_plugin; + +#endif |