.github
LICENSES
android
build
doc
python
src
subprojects
systemd
test
fs
fuzzer
net
playlist
tag
time
util
ConfigGlue.hxx
ContainerScan.cxx
DumpDatabase.cxx
DumpDecoderClient.cxx
DumpDecoderClient.hxx
DumpOgg.cxx
LoadDatabase.cxx
MakeTag.hxx
NullMixerListener.hxx
ParseSongFilter.cxx
ReadApeTags.cxx
ReadFrames.cxx
ReadFrames.hxx
RunChromaprint.cxx
RunCurl.cxx
RunMixRampAnalyzer.cxx
RunReplayGainAnalyzer.cxx
RunZeroconf.cxx
ShutdownHandler.cxx
ShutdownHandler.hxx
TestAudioFormat.cxx
TestIcu.cxx
TestRewindInputStream.cxx
TestStringFilter.cxx
TestTagSongFilter.cxx
WriteFile.cxx
dump_playlist.cxx
dump_rva2.cxx
dump_text_file.cxx
meson.build
read_conf.cxx
read_mixer.cxx
read_tags.cxx
run_convert.cxx
run_decoder.cxx
run_encoder.cxx
run_filter.cxx
run_gunzip.cxx
run_gzip.cxx
run_inotify.cxx
run_input.cxx
run_neighbor_explorer.cxx
run_normalize.cxx
run_output.cxx
run_resolver.cxx
run_storage.cxx
software_volume.cxx
test_archive_bzip2.sh
test_archive_iso9660.sh
test_archive_zzip.sh
test_icy_parser.cxx
test_pcm_channels.cxx
test_pcm_dither.cxx
test_pcm_export.cxx
test_pcm_format.cxx
test_pcm_interleave.cxx
test_pcm_mix.cxx
test_pcm_pack.cxx
test_pcm_util.hxx
test_pcm_volume.cxx
test_protocol.cxx
test_queue_priority.cxx
test_translate_song.cxx
test_vorbis_encoder.cxx
visit_archive.cxx
win32
.clang-format
.gitignore
.readthedocs.yaml
AUTHORS
COPYING
NEWS
README.md
meson.build
meson_options.txt
mpd.svg
valgrind.suppressions

This allows FilterPCM() to return more data, which some implementations may need to do (e.g. FFmpeg).
144 lines
3.2 KiB
C++
144 lines
3.2 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// Copyright The Music Player Daemon Project
|
|
|
|
#include "ConfigGlue.hxx"
|
|
#include "ReadFrames.hxx"
|
|
#include "cmdline/OptionDef.hxx"
|
|
#include "cmdline/OptionParser.hxx"
|
|
#include "lib/fmt/AudioFormatFormatter.hxx"
|
|
#include "lib/fmt/RuntimeError.hxx"
|
|
#include "fs/Path.hxx"
|
|
#include "fs/NarrowPath.hxx"
|
|
#include "filter/LoadOne.hxx"
|
|
#include "filter/Filter.hxx"
|
|
#include "filter/Prepared.hxx"
|
|
#include "pcm/AudioParser.hxx"
|
|
#include "pcm/AudioFormat.hxx"
|
|
#include "pcm/Volume.hxx"
|
|
#include "mixer/Control.hxx"
|
|
#include "system/Error.hxx"
|
|
#include "io/FileDescriptor.hxx"
|
|
#include "util/StringBuffer.hxx"
|
|
#include "util/PrintException.hxx"
|
|
#include "LogBackend.hxx"
|
|
|
|
#include <cassert>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
struct CommandLine {
|
|
FromNarrowPath config_path;
|
|
|
|
const char *filter_name = nullptr;
|
|
|
|
AudioFormat audio_format{44100, SampleFormat::S16, 2};
|
|
|
|
bool verbose = false;
|
|
};
|
|
|
|
enum Option {
|
|
OPTION_VERBOSE,
|
|
};
|
|
|
|
static constexpr OptionDef option_defs[] = {
|
|
{"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_VERBOSE:
|
|
c.verbose = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto args = option_parser.GetRemaining();
|
|
if (args.size() < 2 || args.size() > 3)
|
|
throw std::runtime_error("Usage: run_filter CONFIG NAME [FORMAT] <IN");
|
|
|
|
c.config_path = args[0];
|
|
c.filter_name = args[1];
|
|
|
|
if (args.size() > 2)
|
|
c.audio_format = ParseAudioFormat(args[2], false);
|
|
|
|
return c;
|
|
}
|
|
|
|
static std::unique_ptr<PreparedFilter>
|
|
LoadFilter(const ConfigData &config, const char *name)
|
|
{
|
|
const auto *param = config.FindBlock(ConfigBlockOption::AUDIO_FILTER,
|
|
"name", name);
|
|
if (param == nullptr)
|
|
throw FmtRuntimeError("No such configured filter: {}",
|
|
name);
|
|
|
|
return filter_configured_new(*param);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
try {
|
|
const auto c = ParseCommandLine(argc, argv);
|
|
SetLogThreshold(c.verbose ? LogLevel::DEBUG : LogLevel::INFO);
|
|
|
|
/* read configuration file (mpd.conf) */
|
|
|
|
const auto config = AutoLoadConfigFile(c.config_path);
|
|
|
|
auto audio_format = c.audio_format;
|
|
const size_t in_frame_size = audio_format.GetFrameSize();
|
|
|
|
/* initialize the filter */
|
|
|
|
auto prepared_filter = LoadFilter(config, argv[2]);
|
|
|
|
/* open the filter */
|
|
|
|
auto filter = prepared_filter->Open(audio_format);
|
|
|
|
const AudioFormat out_audio_format = filter->GetOutAudioFormat();
|
|
fmt::print(stderr, "audio_format={}\n", out_audio_format);
|
|
|
|
/* play */
|
|
|
|
FileDescriptor input_fd(STDIN_FILENO);
|
|
FileDescriptor output_fd(STDOUT_FILENO);
|
|
|
|
while (true) {
|
|
std::byte buffer[4096];
|
|
|
|
ssize_t nbytes = ReadFrames(input_fd, buffer, sizeof(buffer),
|
|
in_frame_size);
|
|
if (nbytes == 0)
|
|
break;
|
|
|
|
for (auto dest = filter->FilterPCM(std::span{buffer}.first(nbytes));
|
|
!dest.empty(); dest = filter->ReadMore())
|
|
output_fd.FullWrite(dest);
|
|
}
|
|
|
|
while (true) {
|
|
auto dest = filter->Flush();
|
|
if (dest.empty())
|
|
break;
|
|
output_fd.FullWrite(dest);
|
|
}
|
|
|
|
/* cleanup and exit */
|
|
|
|
return EXIT_SUCCESS;
|
|
} catch (...) {
|
|
PrintException(std::current_exception());
|
|
return EXIT_FAILURE;
|
|
}
|