diff options
author | Max Kellermann <max@musicpd.org> | 2018-08-10 18:06:50 +0200 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2018-10-24 10:52:45 +0200 |
commit | 30e22b753b06660d888eb42a8c74d950d044d6dd (patch) | |
tree | 3f19a698a757949c4562bf1b8b7f08e6df151dbd /test/RunChromaprint.cxx | |
parent | f7141c920121e9c6cef6582c89a73da74b814e2e (diff) |
tag/Chromaprint: OO wrapper for a ChromaprintContext
Diffstat (limited to 'test/RunChromaprint.cxx')
-rw-r--r-- | test/RunChromaprint.cxx | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/test/RunChromaprint.cxx b/test/RunChromaprint.cxx new file mode 100644 index 000000000..311ad4eb7 --- /dev/null +++ b/test/RunChromaprint.cxx @@ -0,0 +1,273 @@ +/* + * 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. + */ + +#include "config.h" +#include "config/File.hxx" +#include "config/Migrate.hxx" +#include "config/Data.hxx" +#include "tag/Chromaprint.hxx" +#include "pcm/PcmConvert.hxx" +#include "event/Thread.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "decoder/Client.hxx" +#include "input/Init.hxx" +#include "input/InputStream.hxx" +#include "fs/Path.hxx" +#include "AudioFormat.hxx" +#include "util/OptionDef.hxx" +#include "util/OptionParser.hxx" +#include "util/PrintException.hxx" +#include "Log.hxx" +#include "LogBackend.hxx" + +#include <stdexcept> + +#include <assert.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +struct CommandLine { + const char *decoder = nullptr; + const char *uri = nullptr; + + Path config_path = nullptr; + + bool verbose = false; +}; + +enum Option { + OPTION_CONFIG, + OPTION_VERBOSE, +}; + +static constexpr OptionDef option_defs[] = { + {"config", 0, true, "Load a MPD configuration file"}, + {"verbose", 'v', false, "Verbose logging"}, +}; + +static CommandLine +ParseCommandLine(int argc, char **argv) +{ + CommandLine c; + + OptionParser option_parser(option_defs, argc, argv); + while (auto o = option_parser.Next()) { + switch (Option(o.index)) { + case OPTION_CONFIG: + c.config_path = Path::FromFS(o.value); + break; + + case OPTION_VERBOSE: + c.verbose = true; + break; + } + } + + auto args = option_parser.GetRemaining(); + if (args.size != 2) + throw std::runtime_error("Usage: RunChromaprint [--verbose] [--config=FILE] DECODER URI"); + + c.decoder = args[0]; + c.uri = args[1]; + return c; +} + +class GlobalInit { + ConfigData config; + EventThread io_thread; + +public: + GlobalInit(Path config_path, bool verbose) { + SetLogThreshold(verbose ? LogLevel::DEBUG : LogLevel::INFO); + + if (!config_path.IsNull()) { + ReadConfigFile(config, config_path); + Migrate(config); + } + + io_thread.Start(); + + input_stream_global_init(config, + io_thread.GetEventLoop()); + decoder_plugin_init_all(config); + + pcm_convert_global_init(config); + } + + ~GlobalInit() { + decoder_plugin_deinit_all(); + input_stream_global_finish(); + } +}; + +class ChromaprintDecoderClient final : public DecoderClient { + bool ready = false; + + bool need_convert = false; + + PcmConvert convert; + + Chromaprint::Context chromaprint; + + uint64_t remaining_bytes; + +public: + Mutex mutex; + + ~ChromaprintDecoderClient() noexcept { + if (need_convert) + convert.Close(); + } + + void PrintResult(); + + /* virtual methods from DecoderClient */ + void Ready(AudioFormat audio_format, + bool seekable, SignedSongTime duration) override; + + DecoderCommand GetCommand() noexcept override { + return remaining_bytes > 0 + ? DecoderCommand::NONE + : DecoderCommand::STOP; + } + + void CommandFinished() override {} + + SongTime GetSeekTime() noexcept override { + return SongTime::zero(); + } + + uint64_t GetSeekFrame() noexcept override { + return 0; + } + + void SeekError() override {} + + InputStreamPtr OpenUri(const char *) override { + throw std::runtime_error("Not implemented"); + } + + size_t Read(InputStream &is, void *buffer, size_t length) override { + return is.LockRead(buffer, length); + } + + void SubmitTimestamp(FloatDuration) override {} + DecoderCommand SubmitData(InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate) override; + + DecoderCommand SubmitTag(InputStream *, Tag &&) override { + return GetCommand(); + } + + void SubmitReplayGain(const ReplayGainInfo *) {} + void SubmitMixRamp(MixRampInfo &&) {} +}; + +void +ChromaprintDecoderClient::PrintResult() +{ + if (!ready) + throw std::runtime_error("Decoding failed"); + + if (need_convert) { + auto flushed = convert.Flush(); + auto data = ConstBuffer<int16_t>::FromVoid(flushed); + chromaprint.Feed(data.data, data.size); + } + + chromaprint.Finish(); + + printf("%s\n", chromaprint.GetFingerprint().c_str()); +} + +void +ChromaprintDecoderClient::Ready(AudioFormat audio_format, bool, SignedSongTime) +{ + /* feed the first two minutes into libchromaprint */ + remaining_bytes = audio_format.TimeToSize(std::chrono::minutes(2)); + + if (audio_format.format != SampleFormat::S16) { + const AudioFormat src_audio_format = audio_format; + audio_format.format = SampleFormat::S16; + + convert.Open(src_audio_format, audio_format); + need_convert = true; + } + + chromaprint.Start(audio_format.sample_rate, audio_format.channels); + + ready = true; +} + +DecoderCommand +ChromaprintDecoderClient::SubmitData(InputStream *, + const void *_data, size_t length, + uint16_t) +{ + if (length > remaining_bytes) + remaining_bytes = 0; + else + remaining_bytes -= length; + + ConstBuffer<void> src{_data, length}; + ConstBuffer<int16_t> data; + + if (need_convert) { + auto result = convert.Convert(src); + data = ConstBuffer<int16_t>::FromVoid(result); + } else + data = ConstBuffer<int16_t>::FromVoid(src); + + chromaprint.Feed(data.data, data.size); + + return GetCommand(); +} + +int main(int argc, char **argv) +try { + const auto c = ParseCommandLine(argc, argv); + + const GlobalInit init(c.config_path, c.verbose); + + const DecoderPlugin *plugin = decoder_plugin_from_name(c.decoder); + if (plugin == nullptr) { + fprintf(stderr, "No such decoder: %s\n", c.decoder); + return EXIT_FAILURE; + } + + ChromaprintDecoderClient client; + if (plugin->file_decode != nullptr) { + plugin->FileDecode(client, Path::FromFS(c.uri)); + } else if (plugin->stream_decode != nullptr) { + auto is = InputStream::OpenReady(c.uri, client.mutex); + plugin->StreamDecode(client, *is); + } else { + fprintf(stderr, "Decoder plugin is not usable\n"); + return EXIT_FAILURE; + } + + client.PrintResult(); + return EXIT_SUCCESS; +} catch (...) { + PrintException(std::current_exception()); + return EXIT_FAILURE; +} |