Another 1.0 candidate before I start stripping meson options

main
Jay Moore 3 days ago
parent 6075950a35
commit 077d715da0

@ -469,12 +469,13 @@ subdir('src/tag')
subdir('src/neighbor')
subdir('src/input')
subdir('src/archive')
subdir('src/filter')
subdir('src/filter') # Stub implementations for compatibility
subdir('src/mixer')
subdir('src/output')
subdir('src/lib/xiph')
subdir('src/decoder')
subdir('src/encoder')
# subdir('src/encoder') # Removed - not needed for database creation
encoder_glue_dep = declare_dependency() # Empty dependency for compatibility
subdir('src/song')
subdir('src/playlist')
@ -605,7 +606,7 @@ mpd_dbcreate = executable(
sqlite_dep,
neighbor_glue_dep,
output_glue_dep,
encoder_glue_dep,
# encoder_glue_dep, # Removed - not needed for database creation
mixer_glue_dep,
more_deps,
fmt_dep,

@ -4,7 +4,7 @@ option('manpages', type: 'boolean', value: false, description: 'Build manual pag
option('doxygen', type: 'boolean', value: false, description: 'Build doxygen source documentation')
option('syslog', type: 'feature', description: 'syslog support')
option('inotify', type: 'boolean', value: true, description: 'inotify support (for automatic database update)')
option('inotify', type: 'boolean', value: false, description: 'inotify support (for automatic database update)')
option('io_uring', type: 'feature', description: 'Linux io_uring support using liburing')
# Daemon and systemd options removed - mpd-dbcreate is a standalone utility, not a daemon
@ -38,23 +38,13 @@ option('dsd', type: 'boolean', value: true, description: 'Support the DSD audio
#
option('database', type: 'boolean', value: true, description: 'enable support for the music database')
option('upnp', type: 'combo',
choices: ['auto', 'pupnp', 'npupnp', 'disabled'],
value: 'auto',
description: 'UPnP client support')
#
# Neighbor plugins
#
option('neighbor', type: 'boolean', value: true, description: 'enable support for neighbor discovery')
#
# Storage plugins
#
option('udisks', type: 'feature', description: 'Support for removable media using udisks2')
option('webdav', type: 'feature', description: 'WebDAV support using CURL and Expat')
#
# Playlist plugins
@ -76,11 +66,6 @@ option('nfs', type: 'feature', description: 'NFS protocol support using libnfs')
# https://bugzilla.samba.org/show_bug.cgi?id=11413
option('smbclient', type: 'feature', value: 'disabled', description: 'SMB support using libsmbclient')
#
# Commercial services
#
option('qobuz', type: 'feature', description: 'Qobuz client')
#
# Archive plugins
@ -122,42 +107,6 @@ option('vorbis', type: 'feature', description: 'Vorbis decoder plugin')
option('wavpack', type: 'feature', description: 'WavPack decoder plugin')
option('wildmidi', type: 'feature', description: 'WildMidi decoder plugin')
#
# Encoder plugins
#
option('vorbisenc', type: 'feature', description: 'Vorbis encoder plugin')
option('lame', type: 'feature', description: 'LAME MP3 encoder plugin')
option('twolame', type: 'feature', description: 'TwoLAME MP2 encoder plugin')
option('shine', type: 'feature', description: 'shine MP3 encoder plugin')
option('wave_encoder', type: 'boolean', value: true, description: 'PCM wave encoder encoder plugin')
#
# Filter plugins
#
option('libsamplerate', type: 'feature', value: 'disabled', description: 'libsamplerate resampler')
option('soxr', type: 'feature', value: 'disabled', description: 'libsoxr resampler')
#
# Output plugins
#
option('alsa', type: 'feature', value: 'disabled', description: 'ALSA support')
option('ao', type: 'feature', value: 'disabled', description: 'libao output plugin')
option('fifo', type: 'boolean', value: false, description: 'FIFO output plugin')
option('httpd', type: 'boolean', value: false, description: 'HTTP streaming output plugin')
option('jack', type: 'feature', value: 'disabled', description: 'JACK output plugin')
option('openal', type: 'feature', value: 'disabled', description: 'OpenAL output plugin')
option('oss', type: 'feature', value: 'disabled', description: 'Open Sound System support')
option('pipe', type: 'boolean', value: false, description: 'Pipe output plugin')
option('pipewire', type: 'feature', value: 'disabled', description: 'PipeWire support')
option('pulse', type: 'feature', value: 'disabled', description: 'PulseAudio support')
option('recorder', type: 'boolean', value: false, description: 'Recorder output plugin')
option('shout', type: 'feature', value: 'disabled', description: 'Shoutcast streaming support using libshout')
option('snapcast', type: 'boolean', value: false, description: 'Snapcast output plugin')
option('sndio', type: 'feature', value: 'disabled', description: 'sndio output plugin')
option('solaris_output', type: 'feature', value: 'disabled', description: 'Solaris /dev/audio support')
#
# Misc libraries

@ -1,40 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "Configured.hxx"
#include "EncoderList.hxx"
#include "EncoderPlugin.hxx"
#include "config/Block.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "util/StringAPI.hxx"
static const EncoderPlugin &
GetConfiguredEncoderPlugin(const ConfigBlock &block, bool shout_legacy)
{
const char *name = block.GetBlockValue("encoder", nullptr);
if (name == nullptr && shout_legacy)
name = block.GetBlockValue("encoding", nullptr);
if (name == nullptr)
name = "vorbis";
if (shout_legacy) {
if (StringIsEqual(name, "ogg"))
name = "vorbis";
else if (StringIsEqual(name, "mp3"))
name = "lame";
}
const auto plugin = encoder_plugin_get(name);
if (plugin == nullptr)
throw FmtRuntimeError("No such encoder: {}", name);
return *plugin;
}
PreparedEncoder *
CreateConfiguredEncoder(const ConfigBlock &block, bool shout_legacy)
{
return encoder_init(GetConfiguredEncoderPlugin(block, shout_legacy),
block);
}

@ -1,23 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Encoder support removed for mpd-dbcreate
#ifndef MPD_ENCODER_CONFIGURED_HXX
#define MPD_ENCODER_CONFIGURED_HXX
#include "EncoderInterface.hxx"
struct ConfigBlock;
class PreparedEncoder;
class AudioFormat;
/**
* Create a #PreparedEncoder instance from the settings in the
* #ConfigBlock. Its "encoder" setting is used to choose the encoder
* plugin.
*
* Throws an exception on error.
*
* @param shout_legacy enable the "shout" plugin legacy configuration?
* i.e. fall back to setting "encoding" instead of "encoder"
* Stub implementation - encoder support not needed for database creation
*/
PreparedEncoder *
CreateConfiguredEncoder(const ConfigBlock &block, bool shout_legacy=false);
inline EncoderPtr
encoder_init([[maybe_unused]] const ConfigBlock &block,
[[maybe_unused]] AudioFormat &audio_format)
{
return nullptr;
}
#endif

@ -1,22 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
/*
* This header is included by encoder plugins.
*
*/
#ifndef MPD_ENCODER_API_HXX
#define MPD_ENCODER_API_HXX
// IWYU pragma: begin_exports
#include "EncoderInterface.hxx"
#include "EncoderPlugin.hxx"
#include "pcm/AudioFormat.hxx"
#include "tag/Tag.hxx"
#include "config/Block.hxx"
// IWYU pragma: end_exports
#endif

@ -1,122 +1,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Encoder support removed for mpd-dbcreate
#ifndef MPD_ENCODER_INTERFACE_HXX
#define MPD_ENCODER_INTERFACE_HXX
#include <cstddef>
#include <span>
struct AudioFormat;
struct Tag;
#include <memory>
/**
* Stub implementation - encoder support not needed for database creation
*/
class Encoder {
const bool implements_tag;
public:
explicit Encoder(bool _implements_tag) noexcept
:implements_tag(_implements_tag) {}
virtual ~Encoder() noexcept = default;
bool ImplementsTag() const noexcept {
return implements_tag;
}
/**
* Ends the stream: flushes the encoder object, generate an
* end-of-stream marker (if applicable), make everything which
* might currently be buffered available by Read().
*
* After this function has been called, the encoder may not be
* usable for more data, and only Read() can be called.
*
* Throws on error.
*/
virtual void End() {
}
/**
* Flushes an encoder object, make everything which might
* currently be buffered available by Read().
*
* Throws on error.
*/
virtual void Flush() {
}
/**
* Prepare for sending a tag to the encoder. This is used by
* some encoders to flush the previous sub-stream, in
* preparation to begin a new one.
*
* Throws on error.
*/
virtual void PreTag() {
}
/**
* Sends a tag to the encoder.
*
* Instructions: call PreTag(); then obtain flushed data with
* Read(); finally call Tag() and again Read().
*
* Throws on error.
*
* @param tag the tag object
*/
virtual void SendTag([[maybe_unused]] const Tag &tag) {
}
/**
* Writes raw PCM data to the encoder.
*
* Throws on error.
*
* @param data the buffer containing PCM samples
* @param length the length of the buffer in bytes
*/
virtual void Write(std::span<const std::byte> src) = 0;
/**
* Reads encoded data from the encoder.
*
* Call this repeatedly after End(), Flush(), PreTag(), SendTag() and
* Write() until no more data is returned.
*
* @param buffer a buffer that can be used to write data into
*
* @return the portion of the buffer that was filled (but may
* also point to a different buffer, e.g. one owned by this object)
*/
virtual std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept = 0;
virtual ~Encoder() = default;
};
class PreparedEncoder {
public:
virtual ~PreparedEncoder() noexcept = default;
/**
* Create an #Encoder instance.
*
* After this function returns successfully and before the
* first Encoder::Write() call, you should invoke
* Encoder::Read() to obtain the file header.
*
* Throws on error.
*
* @param audio_format the encoder's input audio format; the plugin
* may modify the struct to adapt it to its abilities
*/
virtual Encoder *Open(AudioFormat &audio_format) = 0;
/**
* Get mime type of encoded content.
*
* @return an constant string, nullptr on failure
*/
virtual const char *GetMimeType() const noexcept {
return nullptr;
}
};
using EncoderPtr = std::unique_ptr<Encoder>;
#endif

@ -1,55 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "config.h"
#include "EncoderList.hxx"
#include "EncoderPlugin.hxx"
#include "encoder/Features.h"
#include "plugins/NullEncoderPlugin.hxx"
#include "plugins/WaveEncoderPlugin.hxx"
#include "plugins/VorbisEncoderPlugin.hxx"
#include "plugins/OpusEncoderPlugin.hxx"
#include "plugins/FlacEncoderPlugin.hxx"
#include "plugins/ShineEncoderPlugin.hxx"
#include "plugins/LameEncoderPlugin.hxx"
#include "plugins/TwolameEncoderPlugin.hxx"
#include "decoder/Features.h"
#include <string.h>
constinit const EncoderPlugin *const encoder_plugins[] = {
&null_encoder_plugin,
#ifdef ENABLE_VORBISENC
&vorbis_encoder_plugin,
#endif
#ifdef ENABLE_OPUS
&opus_encoder_plugin,
#endif
#ifdef ENABLE_LAME
&lame_encoder_plugin,
#endif
#ifdef ENABLE_TWOLAME
&twolame_encoder_plugin,
#endif
#ifdef ENABLE_WAVE_ENCODER
&wave_encoder_plugin,
#endif
#ifdef ENABLE_FLAC_ENCODER
&flac_encoder_plugin,
#endif
#ifdef ENABLE_SHINE
&shine_encoder_plugin,
#endif
nullptr
};
const EncoderPlugin *
encoder_plugin_get(const char *name)
{
for (const auto &plugin : GetAllEncoderPlugins()) {
if (strcmp(plugin.name, name) == 0)
return &plugin;
}
return nullptr;
}

@ -1,27 +1,41 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Encoder support removed for mpd-dbcreate
#pragma once
#ifndef MPD_ENCODER_LIST_HXX
#define MPD_ENCODER_LIST_HXX
#include "util/DereferenceIterator.hxx"
#include "util/TerminatedArray.hxx"
#include <iterator>
struct EncoderPlugin;
extern const EncoderPlugin *const encoder_plugins[];
/**
* Stub implementation - encoder support not needed for database creation
*/
class EncoderPluginIterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = const EncoderPlugin *;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
static inline auto
GetAllEncoderPlugins() noexcept
bool operator==(const EncoderPluginIterator &) const noexcept { return true; }
bool operator!=(const EncoderPluginIterator &) const noexcept { return false; }
EncoderPluginIterator &operator++() noexcept { return *this; }
const EncoderPlugin *operator*() const noexcept { return nullptr; }
};
inline EncoderPluginIterator
encoder_plugins_begin() noexcept
{
return DereferenceContainerAdapter{TerminatedArray<const EncoderPlugin *const, nullptr>{encoder_plugins}};
return {};
}
/**
* Looks up an encoder plugin by its name.
*
* @param name the encoder name to look for
* @return the encoder plugin with the specified name, or nullptr if none
* was found
*/
const EncoderPlugin *
encoder_plugin_get(const char *name);
inline EncoderPluginIterator
encoder_plugins_end() noexcept
{
return {};
}
#endif

@ -1,32 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Encoder support removed for mpd-dbcreate
#ifndef MPD_ENCODER_PLUGIN_HXX
#define MPD_ENCODER_PLUGIN_HXX
class PreparedEncoder;
struct ConfigBlock;
/**
* Stub implementation - encoder support not needed for database creation
*/
struct EncoderPlugin {
const char *name;
/**
* Throws #std::runtime_error on error.
*/
PreparedEncoder *(*init)(const ConfigBlock &block);
};
/**
* Creates a new encoder object.
*
* Throws #std::runtime_error on error.
*
* @param plugin the encoder plugin
*/
static inline PreparedEncoder *
encoder_init(const EncoderPlugin &plugin, const ConfigBlock &block)
{
return plugin.init(block);
}
#endif

@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Encoder support removed for mpd-dbcreate
#ifndef MPD_ENCODER_FEATURES_H
#define MPD_ENCODER_FEATURES_H
// Encoder support disabled for database creation tool
#define ENABLE_ENCODER 0
#endif

@ -1,23 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "ToOutputStream.hxx"
#include "EncoderInterface.hxx"
#include "io/OutputStream.hxx"
void
EncoderToOutputStream(OutputStream &os, Encoder &encoder)
{
while (true) {
/* read from the encoder */
std::byte buffer[32768];
const auto r = encoder.Read(buffer);
if (r.empty())
return;
/* write everything to the stream */
os.Write(r);
}
}

@ -1,16 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#pragma once
class OutputStream;
class Encoder;
/**
* Read all available output from the #Encoder and write it to the
* #OutputStream.
*
* Throws on error.
*/
void
EncoderToOutputStream(OutputStream &os, Encoder &encoder);

@ -1,50 +0,0 @@
encoder_features = configuration_data()
encoder_features.set('ENABLE_ENCODER', need_encoder)
if not need_encoder
if need_wave_encoder
# Special case for the Snapcast output plugin which only needs the
# PCM wave encoder encoder plugin
encoder_glue = static_library(
'encoder_glue',
'plugins/WaveEncoderPlugin.cxx',
include_directories: inc,
)
encoder_glue_dep = declare_dependency(
link_with: encoder_glue,
)
configure_file(output: 'Features.h', configuration: encoder_features)
subdir_done()
endif
encoder_glue_dep = dependency('', required: false)
configure_file(output: 'Features.h', configuration: encoder_features)
subdir_done()
endif
encoder_api_dep = declare_dependency()
subdir('plugins')
encoder_glue = static_library(
'encoder_glue',
'Configured.cxx',
'ToOutputStream.cxx',
'EncoderList.cxx',
include_directories: inc,
dependencies: [
fmt_dep,
],
)
encoder_glue_dep = declare_dependency(
link_with: encoder_glue,
dependencies: [
encoder_plugins_dep,
],
)
configure_file(output: 'Features.h', configuration: encoder_features)

@ -1,288 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "FlacEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "tag/Names.hxx"
#include "pcm/AudioFormat.hxx"
#include "pcm/Buffer.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include "util/Serial.hxx"
#include "util/SpanCast.hxx"
#include "util/StringUtil.hxx"
#include <FLAC/stream_encoder.h>
#include <FLAC/metadata.h>
#include <algorithm>
#include <utility> // for std::unreachable()
class FlacEncoder final : public Encoder {
const AudioFormat audio_format;
FLAC__StreamEncoder *const fse;
const unsigned compression;
const bool oggflac;
PcmBuffer expand_buffer;
/**
* This buffer will hold encoded data from libFLAC until it is
* picked up with Read().
*/
DynamicFifoBuffer<std::byte> output_buffer{8192};
public:
FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining);
~FlacEncoder() noexcept override {
FLAC__stream_encoder_delete(fse);
}
FlacEncoder(const FlacEncoder &) = delete;
FlacEncoder &operator=(const FlacEncoder &) = delete;
/* virtual methods from class Encoder */
void End() override {
(void) FLAC__stream_encoder_finish(fse);
}
void Flush() override {
}
void PreTag() override {
(void) FLAC__stream_encoder_finish(fse);
}
void SendTag(const Tag &tag) override;
void Write(std::span<const std::byte> src) override;
std::span<const std::byte> Read(std::span<std::byte>) noexcept override {
auto r = output_buffer.Read();
output_buffer.Consume(r.size());
return r;
}
private:
static FLAC__StreamEncoderWriteStatus WriteCallback(const FLAC__StreamEncoder *,
const FLAC__byte data[],
size_t bytes,
[[maybe_unused]] unsigned samples,
[[maybe_unused]] unsigned current_frame,
void *client_data) noexcept {
auto &encoder = *(FlacEncoder *)client_data;
encoder.output_buffer.Append({(const std::byte *)data, bytes});
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
}
};
class PreparedFlacEncoder final : public PreparedEncoder {
const unsigned compression;
const bool oggchaining;
const bool oggflac;
public:
explicit PreparedFlacEncoder(const ConfigBlock &block);
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &audio_format) override;
[[nodiscard]] const char *GetMimeType() const noexcept override {
if(oggflac)
return "audio/ogg";
return "audio/flac";
}
};
PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block)
:compression(block.GetBlockValue("compression", 5U)),
oggchaining(block.GetBlockValue("oggchaining",false)),
oggflac(block.GetBlockValue("oggflac",false) || oggchaining)
{
}
static PreparedEncoder *
flac_encoder_init(const ConfigBlock &block)
{
return new PreparedFlacEncoder(block);
}
static void
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
const AudioFormat &audio_format)
{
unsigned bits_per_sample;
switch (audio_format.format) {
case SampleFormat::S8:
bits_per_sample = 8;
break;
case SampleFormat::S16:
bits_per_sample = 16;
break;
default:
bits_per_sample = 24;
}
if (!FLAC__stream_encoder_set_compression_level(fse, compression))
throw FmtRuntimeError("error setting flac compression to {}",
compression);
if (!FLAC__stream_encoder_set_channels(fse, audio_format.channels))
throw FmtRuntimeError("error setting flac channels num to {}",
audio_format.channels);
if (!FLAC__stream_encoder_set_bits_per_sample(fse, bits_per_sample))
throw FmtRuntimeError("error setting flac bit format to {}",
bits_per_sample);
if (!FLAC__stream_encoder_set_sample_rate(fse,
audio_format.sample_rate))
throw FmtRuntimeError("error setting flac sample rate to {}",
audio_format.sample_rate);
if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
GenerateSerial()))
throw std::runtime_error{"error setting ogg serial number"};
}
FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining)
:Encoder(_oggchaining),
audio_format(_audio_format), fse(_fse),
compression(_compression),
oggflac(_oggflac)
{
/* this immediately outputs data through callback */
auto init_status = oggflac ?
FLAC__stream_encoder_init_ogg_stream(fse,
nullptr, WriteCallback,
nullptr, nullptr, nullptr,
this)
:
FLAC__stream_encoder_init_stream(fse,
WriteCallback,
nullptr, nullptr, nullptr,
this);
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
throw FmtRuntimeError("failed to initialize encoder: {}",
FLAC__StreamEncoderInitStatusString[init_status]);
}
Encoder *
PreparedFlacEncoder::Open(AudioFormat &audio_format)
{
switch (audio_format.format) {
case SampleFormat::S8:
break;
case SampleFormat::S16:
break;
case SampleFormat::S24_P32:
break;
default:
audio_format.format = SampleFormat::S24_P32;
}
/* allocate the encoder */
auto fse = FLAC__stream_encoder_new();
if (fse == nullptr)
throw std::runtime_error("FLAC__stream_encoder_new() failed");
try {
flac_encoder_setup(fse, compression, oggflac, audio_format);
} catch (...) {
FLAC__stream_encoder_delete(fse);
throw;
}
return new FlacEncoder(audio_format, fse, compression, oggflac, oggchaining);
}
void
FlacEncoder::SendTag(const Tag &tag)
{
/* re-initialize encoder since flac_encoder_finish resets everything */
flac_encoder_setup(fse, compression, oggflac, audio_format);
FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
FLAC__StreamMetadata_VorbisComment_Entry entry;
for (const auto &item : tag) {
char name[64];
ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, name, item.value);
FLAC__metadata_object_vorbiscomment_append_comment(metadata, entry, false);
}
FLAC__stream_encoder_set_metadata(fse,&metadata,1);
auto init_status = FLAC__stream_encoder_init_ogg_stream(fse,
nullptr, WriteCallback,
nullptr, nullptr, nullptr,
this);
FLAC__metadata_object_delete(metadata);
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
throw FmtRuntimeError("failed to initialize encoder: {}",
FLAC__StreamEncoderInitStatusString[init_status]);
}
template<typename T>
static std::span<const FLAC__int32>
ToFlac32(PcmBuffer &buffer, std::span<const T> src) noexcept
{
FLAC__int32 *dest = buffer.GetT<FLAC__int32>(src.size());
std::copy(src.begin(), src.end(), dest);
return {dest, src.size()};
}
static std::span<const FLAC__int32>
ToFlac32(PcmBuffer &buffer, std::span<const std::byte> src,
SampleFormat format)
{
switch (format) {
case SampleFormat::S8:
return ToFlac32(buffer, FromBytesStrict<const int8_t>(src));
case SampleFormat::S16:
return ToFlac32(buffer, FromBytesStrict<const int16_t>(src));
case SampleFormat::S24_P32:
case SampleFormat::S32:
/* nothing need to be done; format is the same for
both mpd and libFLAC */
return FromBytesStrict<const int32_t>(src);
default:
std::unreachable();
}
}
void
FlacEncoder::Write(std::span<const std::byte> src)
{
const auto imported = ToFlac32(expand_buffer, src,
audio_format.format);
const std::size_t n_frames = imported.size() / audio_format.channels;
/* feed samples to encoder */
if (!FLAC__stream_encoder_process_interleaved(fse, imported.data(),
n_frames))
throw std::runtime_error("flac encoder process failed");
}
const EncoderPlugin flac_encoder_plugin = {
"flac",
flac_encoder_init,
};

@ -1,9 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ENCODER_FLAC_HXX
#define MPD_ENCODER_FLAC_HXX
extern const struct EncoderPlugin flac_encoder_plugin;
#endif

@ -1,187 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "LameEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "pcm/AudioFormat.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "util/CNumberParser.hxx"
#include "util/ReusableArray.hxx"
#include "util/SpanCast.hxx"
#include <lame/lame.h>
#include <cassert>
#include <stdexcept>
class LameEncoder final : public Encoder {
lame_global_flags *const gfp;
ReusableArray<std::byte, 32768> output_buffer;
std::span<const std::byte> output{};
public:
static constexpr unsigned CHANNELS = 2;
explicit LameEncoder(lame_global_flags *_gfp) noexcept
:Encoder(false), gfp(_gfp) {}
~LameEncoder() noexcept override;
LameEncoder(const LameEncoder &) = delete;
LameEncoder &operator=(const LameEncoder &) = delete;
/* virtual methods from class Encoder */
void Write(std::span<const std::byte> src) override;
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override;
};
class PreparedLameEncoder final : public PreparedEncoder {
float quality;
int bitrate;
public:
explicit PreparedLameEncoder(const ConfigBlock &block);
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &audio_format) override;
[[nodiscard]] const char *GetMimeType() const noexcept override {
return "audio/mpeg";
}
};
PreparedLameEncoder::PreparedLameEncoder(const ConfigBlock &block)
{
const char *value;
char *endptr;
value = block.GetBlockValue("quality");
if (value != nullptr) {
/* a quality was configured (VBR) */
quality = float(ParseDouble(value, &endptr));
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
throw FmtRuntimeError("quality {:?} is not a number in the "
"range -1 to 10",
value);
if (block.GetBlockValue("bitrate") != nullptr)
throw std::runtime_error("quality and bitrate are both defined");
} else {
/* a bit rate was configured */
value = block.GetBlockValue("bitrate");
if (value == nullptr)
throw std::runtime_error("neither bitrate nor quality defined");
quality = -2.0;
bitrate = ParseInt(value, &endptr);
if (*endptr != '\0' || bitrate <= 0)
throw std::runtime_error("bitrate should be a positive integer");
}
}
static PreparedEncoder *
lame_encoder_init(const ConfigBlock &block)
{
return new PreparedLameEncoder(block);
}
static void
lame_encoder_setup(lame_global_flags *gfp, float quality, int bitrate,
const AudioFormat &audio_format)
{
if (quality >= -1.0f) {
/* a quality was configured (VBR) */
if (0 != lame_set_VBR(gfp, vbr_rh))
throw std::runtime_error("error setting lame VBR mode");
if (0 != lame_set_VBR_q(gfp, int(quality)))
throw std::runtime_error("error setting lame VBR quality");
} else {
/* a bit rate was configured */
if (0 != lame_set_brate(gfp, bitrate))
throw std::runtime_error("error setting lame bitrate");
}
if (0 != lame_set_num_channels(gfp, audio_format.channels))
throw std::runtime_error("error setting lame num channels");
if (0 != lame_set_in_samplerate(gfp, audio_format.sample_rate))
throw std::runtime_error("error setting lame sample rate");
if (0 != lame_set_out_samplerate(gfp, audio_format.sample_rate))
throw std::runtime_error("error setting lame out sample rate");
if (0 > lame_init_params(gfp))
throw std::runtime_error("error initializing lame params");
}
Encoder *
PreparedLameEncoder::Open(AudioFormat &audio_format)
{
audio_format.format = SampleFormat::S16;
audio_format.channels = LameEncoder::CHANNELS;
auto gfp = lame_init();
if (gfp == nullptr)
throw std::runtime_error("lame_init() failed");
try {
lame_encoder_setup(gfp, quality, bitrate, audio_format);
} catch (...) {
lame_close(gfp);
throw;
}
return new LameEncoder(gfp);
}
LameEncoder::~LameEncoder() noexcept
{
lame_close(gfp);
}
void
LameEncoder::Write(std::span<const std::byte> _src)
{
const auto src = FromBytesStrict<const int16_t>(_src);
assert(output.empty());
const std::size_t num_samples = src.size();
const std::size_t num_frames = num_samples / CHANNELS;
/* worst-case formula according to LAME documentation */
const std::size_t output_buffer_size = 5 * num_samples / 4 + 7200;
const auto dest = output_buffer.Get(output_buffer_size);
/* this is for only 16-bit audio */
int bytes_out = lame_encode_buffer_interleaved(gfp,
const_cast<short *>(src.data()),
num_frames,
(unsigned char *)dest,
output_buffer_size);
if (bytes_out < 0)
throw std::runtime_error("lame encoder failed");
output = {dest, std::size_t(bytes_out)};
}
std::span<const std::byte>
LameEncoder::Read(std::span<std::byte>) noexcept
{
return std::exchange(output, std::span<const std::byte>{});
}
const EncoderPlugin lame_encoder_plugin = {
"lame",
lame_encoder_init,
};

@ -1,9 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ENCODER_LAME_HXX
#define MPD_ENCODER_LAME_HXX
extern const struct EncoderPlugin lame_encoder_plugin;
#endif

@ -1,42 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "NullEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "util/DynamicFifoBuffer.hxx"
class NullEncoder final : public Encoder {
DynamicFifoBuffer<std::byte> buffer{8192};
public:
NullEncoder()
:Encoder(false) {}
/* virtual methods from class Encoder */
void Write(std::span<const std::byte> src) override {
buffer.Append(src);
}
std::span<const std::byte> Read(std::span<std::byte> b) noexcept override {
return b.first(buffer.Read(b.data(), b.size()));
}
};
class PreparedNullEncoder final : public PreparedEncoder {
public:
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &) override {
return new NullEncoder();
}
};
static PreparedEncoder *
null_encoder_init([[maybe_unused]] const ConfigBlock &block)
{
return new PreparedNullEncoder();
}
const EncoderPlugin null_encoder_plugin = {
"null",
null_encoder_init,
};

@ -1,9 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ENCODER_NULL_HXX
#define MPD_ENCODER_NULL_HXX
extern const struct EncoderPlugin null_encoder_plugin;
#endif

@ -1,55 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_OGG_ENCODER_HXX
#define MPD_OGG_ENCODER_HXX
#include "../EncoderAPI.hxx"
#include "lib/xiph/OggStreamState.hxx"
#include "lib/xiph/OggPage.hxx"
#include "util/Serial.hxx"
#include <ogg/ogg.h>
/**
* An abstract base class which contains code common to all encoders
* with Ogg container output.
*/
class OggEncoder : public Encoder {
/* initialize "flush" to true, so the caller gets the full
headers on the first read */
bool flush = true;
protected:
OggStreamState stream;
public:
OggEncoder(bool _implements_tag)
:Encoder(_implements_tag),
stream(GenerateSerial()) {
}
/* virtual methods from class Encoder */
void Flush() final {
flush = true;
}
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override {
ogg_page page;
bool success = stream.PageOut(page);
if (!success) {
if (flush) {
flush = false;
success = stream.Flush(page);
}
if (!success)
return {};
}
return buffer.first(ReadPage(page, buffer.data(),
buffer.size()));
}
};
#endif

@ -1,402 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "OpusEncoderPlugin.hxx"
#include "OggEncoder.hxx"
#include "tag/Names.hxx"
#include "pcm/AudioFormat.hxx"
#include "util/ByteOrder.hxx"
#include "util/StringUtil.hxx"
#include <opus.h>
#include <ogg/ogg.h>
#include <cassert>
#include <stdexcept>
#include <stdlib.h>
namespace {
class OpusEncoder final : public OggEncoder {
const AudioFormat audio_format;
const size_t frame_size;
const size_t buffer_frames, buffer_size;
size_t buffer_position = 0;
std::byte *const buffer;
::OpusEncoder *const enc;
unsigned char buffer2[1275 * 3 + 7];
int lookahead;
ogg_int64_t packetno = 0;
ogg_int64_t granulepos = 0;
public:
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc, bool _chaining);
~OpusEncoder() noexcept override;
OpusEncoder(const OpusEncoder &) = delete;
OpusEncoder &operator=(const OpusEncoder &) = delete;
/* virtual methods from class Encoder */
void End() override;
void Write(std::span<const std::byte> src) override;
void PreTag() override;
void SendTag(const Tag &tag) override;
private:
void DoEncode(bool eos);
void WriteSilence(unsigned fill_frames);
void GenerateHeaders(const Tag *tag) noexcept;
void GenerateHead() noexcept;
void GenerateTags(const Tag *tag) noexcept;
};
class PreparedOpusEncoder final : public PreparedEncoder {
opus_int32 bitrate;
int complexity;
int signal;
int packet_loss;
int vbr;
int vbr_constraint;
const bool chaining;
public:
explicit PreparedOpusEncoder(const ConfigBlock &block);
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &audio_format) override;
[[nodiscard]] const char *GetMimeType() const noexcept override {
return "audio/ogg";
}
};
PreparedOpusEncoder::PreparedOpusEncoder(const ConfigBlock &block)
:chaining(block.GetBlockValue("opustags", false))
{
const char *value = block.GetBlockValue("bitrate", "auto");
if (strcmp(value, "auto") == 0)
bitrate = OPUS_AUTO;
else if (strcmp(value, "max") == 0)
bitrate = OPUS_BITRATE_MAX;
else {
char *endptr;
bitrate = strtoul(value, &endptr, 10);
if (endptr == value || *endptr != 0 ||
bitrate < 500 || bitrate > 512000)
throw std::runtime_error("Invalid bit rate");
}
complexity = block.GetBlockValue("complexity", 10U);
if (complexity > 10)
throw std::runtime_error("Invalid complexity");
value = block.GetBlockValue("signal", "auto");
if (strcmp(value, "auto") == 0)
signal = OPUS_AUTO;
else if (strcmp(value, "voice") == 0)
signal = OPUS_SIGNAL_VOICE;
else if (strcmp(value, "music") == 0)
signal = OPUS_SIGNAL_MUSIC;
else
throw std::runtime_error("Invalid signal");
value = block.GetBlockValue("vbr", "yes");
if (strcmp(value, "yes") == 0) {
vbr = 1U;
vbr_constraint = 0U;
} else if (strcmp(value, "no") == 0) {
vbr = 0U;
vbr_constraint = 0U;
} else if (strcmp(value, "constrained") == 0) {
vbr = 1U;
vbr_constraint = 1U;
} else
throw std::runtime_error("Invalid vbr");
packet_loss = block.GetBlockValue("packet_loss", 0U);
if (packet_loss > 100)
throw std::runtime_error("Invalid packet loss");
}
PreparedEncoder *
opus_encoder_init(const ConfigBlock &block)
{
return new PreparedOpusEncoder(block);
}
OpusEncoder::OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc, bool _chaining)
:OggEncoder(_chaining),
audio_format(_audio_format),
frame_size(_audio_format.GetFrameSize()),
buffer_frames(_audio_format.sample_rate / 50),
buffer_size(frame_size * buffer_frames),
buffer(new std::byte[buffer_size]),
enc(_enc)
{
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
GenerateHeaders(nullptr);
}
Encoder *
PreparedOpusEncoder::Open(AudioFormat &audio_format)
{
/* libopus supports only 48 kHz */
audio_format.sample_rate = 48000;
if (audio_format.channels > 2)
audio_format.channels = 1;
switch (audio_format.format) {
case SampleFormat::S16:
case SampleFormat::FLOAT:
break;
case SampleFormat::S8:
audio_format.format = SampleFormat::S16;
break;
default:
audio_format.format = SampleFormat::FLOAT;
break;
}
int error_code;
auto *enc = opus_encoder_create(audio_format.sample_rate,
audio_format.channels,
OPUS_APPLICATION_AUDIO,
&error_code);
if (enc == nullptr)
throw std::runtime_error(opus_strerror(error_code));
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal));
opus_encoder_ctl(enc, OPUS_SET_VBR(vbr));
opus_encoder_ctl(enc, OPUS_SET_VBR_CONSTRAINT(vbr_constraint));
opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(packet_loss));
return new OpusEncoder(audio_format, enc, chaining);
}
OpusEncoder::~OpusEncoder() noexcept
{
delete[] buffer;
opus_encoder_destroy(enc);
}
void
OpusEncoder::DoEncode(bool eos)
{
assert(buffer_position == buffer_size || eos);
opus_int32 result =
audio_format.format == SampleFormat::S16
? opus_encode(enc,
(const opus_int16 *)buffer,
buffer_frames,
buffer2,
sizeof(buffer2))
: opus_encode_float(enc,
(const float *)buffer,
buffer_frames,
buffer2,
sizeof(buffer2));
if (result < 0)
throw std::runtime_error("Opus encoder error");
granulepos += buffer_position / frame_size;
ogg_packet packet;
packet.packet = buffer2;
packet.bytes = result;
packet.b_o_s = false;
packet.e_o_s = eos;
packet.granulepos = granulepos;
packet.packetno = packetno++;
stream.PacketIn(packet);
buffer_position = 0;
}
void
OpusEncoder::End()
{
memset(buffer + buffer_position, 0,
buffer_size - buffer_position);
DoEncode(true);
Flush();
}
void
OpusEncoder::WriteSilence(unsigned fill_frames)
{
size_t fill_bytes = fill_frames * frame_size;
while (fill_bytes > 0) {
size_t nbytes = buffer_size - buffer_position;
if (nbytes > fill_bytes)
nbytes = fill_bytes;
memset(buffer + buffer_position, 0, nbytes);
buffer_position += nbytes;
fill_bytes -= nbytes;
if (buffer_position == buffer_size)
DoEncode(false);
}
}
void
OpusEncoder::Write(std::span<const std::byte> src)
{
if (lookahead > 0) {
/* generate some silence at the beginning of the
stream */
assert(buffer_position == 0);
WriteSilence(lookahead);
lookahead = 0;
}
while (!src.empty()) {
const std::size_t nbytes = std::min(buffer_size - buffer_position,
src.size());
memcpy(buffer + buffer_position, src.data(), nbytes);
src = src.subspan(nbytes);
buffer_position += nbytes;
if (buffer_position == buffer_size)
DoEncode(false);
}
}
void
OpusEncoder::GenerateHeaders(const Tag *tag) noexcept
{
GenerateHead();
GenerateTags(tag);
}
void
OpusEncoder::GenerateHead() noexcept
{
unsigned char header[19];
memcpy(header, "OpusHead", 8);
header[8] = 1;
header[9] = audio_format.channels;
*(uint16_t *)(header + 10) = ToLE16(lookahead);
*(uint32_t *)(header + 12) = ToLE32(audio_format.sample_rate);
header[16] = 0;
header[17] = 0;
header[18] = 0;
ogg_packet packet;
packet.packet = header;
packet.bytes = sizeof(header);
packet.b_o_s = true;
packet.e_o_s = false;
packet.granulepos = 0;
packet.packetno = packetno++;
stream.PacketIn(packet);
// flush not needed because libogg autoflushes on b_o_s flag
}
void
OpusEncoder::GenerateTags(const Tag *tag) noexcept
{
const char *version = opus_get_version_string();
size_t version_length = strlen(version);
// len("OpusTags") + 4 byte version length + len(version) + 4 byte tag count
size_t comments_size = 8 + 4 + version_length + 4;
uint32_t tag_count = 0;
if (tag) {
for (const auto &item: *tag) {
++tag_count;
// 4 byte length + len(tagname) + len('=') + len(value)
comments_size += 4 + strlen(tag_item_names[item.type]) + 1 + strlen(item.value);
}
}
auto *comments = new unsigned char[comments_size];
unsigned char *p = comments;
memcpy(comments, "OpusTags", 8);
*(uint32_t *)(comments + 8) = ToLE32(version_length);
p += 12;
memcpy(p, version, version_length);
p += version_length;
tag_count = ToLE32(tag_count);
memcpy(p, &tag_count, 4);
p += 4;
if (tag) {
for (const auto &item: *tag) {
size_t tag_name_len = strlen(tag_item_names[item.type]);
size_t tag_val_len = strlen(item.value);
uint32_t tag_len_le = ToLE32(tag_name_len + 1 + tag_val_len);
memcpy(p, &tag_len_le, 4);
p += 4;
ToUpperASCII((char *)p, tag_item_names[item.type], tag_name_len + 1);
p += tag_name_len;
*p++ = '=';
memcpy(p, item.value, tag_val_len);
p += tag_val_len;
}
}
assert(comments + comments_size == p);
ogg_packet packet;
packet.packet = comments;
packet.bytes = comments_size;
packet.b_o_s = false;
packet.e_o_s = false;
packet.granulepos = 0;
packet.packetno = packetno++;
stream.PacketIn(packet);
Flush();
delete[] comments;
}
void
OpusEncoder::PreTag()
{
End();
packetno = 0;
granulepos = 0; // not really required, but useful to prevent wraparound
opus_encoder_ctl(enc, OPUS_RESET_STATE);
}
void
OpusEncoder::SendTag(const Tag &tag)
{
stream.Reinitialize(GenerateSerial());
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
GenerateHeaders(&tag);
}
} // namespace
const EncoderPlugin opus_encoder_plugin = {
"opus",
opus_encoder_init,
};

@ -1,9 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ENCODER_OPUS_H
#define MPD_ENCODER_OPUS_H
extern const struct EncoderPlugin opus_encoder_plugin;
#endif

@ -1,193 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "ShineEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "pcm/AudioFormat.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include "util/SpanCast.hxx"
extern "C"
{
#include <shine/layer3.h>
}
static constexpr size_t BUFFER_INIT_SIZE = 8192;
static constexpr unsigned CHANNELS = 2;
class ShineEncoder final : public Encoder {
const AudioFormat audio_format;
const shine_t shine;
const size_t frame_size;
/* workaround for bug:
https://github.com/savonet/shine/issues/11 */
size_t input_pos = SHINE_MAX_SAMPLES + 1;
int16_t *stereo[CHANNELS];
DynamicFifoBuffer<std::byte> output_buffer{BUFFER_INIT_SIZE};
public:
ShineEncoder(AudioFormat _audio_format, shine_t _shine) noexcept
:Encoder(false),
audio_format(_audio_format), shine(_shine),
frame_size(shine_samples_per_pass(shine)),
stereo{new int16_t[frame_size], new int16_t[frame_size]}
{}
~ShineEncoder() noexcept override {
if (input_pos > SHINE_MAX_SAMPLES) {
/* write zero chunk */
input_pos = 0;
WriteChunk(true);
}
shine_close(shine);
delete[] stereo[0];
delete[] stereo[1];
}
bool WriteChunk(bool flush);
/* virtual methods from class Encoder */
void End() override {
return Flush();
}
void Flush() override;
void Write(std::span<const std::byte> src) override;
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override {
return buffer.first(output_buffer.Read(buffer.data(), buffer.size()));
}
};
class PreparedShineEncoder final : public PreparedEncoder {
shine_config_t config;
public:
explicit PreparedShineEncoder(const ConfigBlock &block);
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &audio_format) override;
[[nodiscard]] const char *GetMimeType() const noexcept override {
return "audio/mpeg";
}
};
PreparedShineEncoder::PreparedShineEncoder(const ConfigBlock &block)
{
shine_set_config_mpeg_defaults(&config.mpeg);
config.mpeg.bitr = block.GetBlockValue("bitrate", 128);
}
static PreparedEncoder *
shine_encoder_init(const ConfigBlock &block)
{
return new PreparedShineEncoder(block);
}
static shine_t
SetupShine(shine_config_t config, AudioFormat &audio_format)
{
audio_format.format = SampleFormat::S16;
audio_format.channels = CHANNELS;
config.mpeg.mode = audio_format.channels == 2 ? STEREO : MONO;
config.wave.samplerate = audio_format.sample_rate;
config.wave.channels =
audio_format.channels == 2 ? PCM_STEREO : PCM_MONO;
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0)
throw FmtRuntimeError("error configuring shine. "
"samplerate {} and bitrate {} configuration"
" not supported.",
config.wave.samplerate,
config.mpeg.bitr);
auto shine = shine_initialise(&config);
if (!shine)
throw std::runtime_error("error initializing shine");
return shine;
}
Encoder *
PreparedShineEncoder::Open(AudioFormat &audio_format)
{
auto shine = SetupShine(config, audio_format);
return new ShineEncoder(audio_format, shine);
}
bool
ShineEncoder::WriteChunk(bool flush)
{
if (flush || input_pos == frame_size) {
if (flush) {
/* fill remaining with 0s */
for (; input_pos < frame_size; input_pos++) {
stereo[0][input_pos] = stereo[1][input_pos] = 0;
}
}
int written;
const auto out = (const std::byte *)
shine_encode_buffer(shine, stereo, &written);
if (written > 0)
output_buffer.Append({out, std::size_t(written)});
input_pos = 0;
}
return true;
}
void
ShineEncoder::Write(std::span<const std::byte> _src)
{
const auto src = FromBytesStrict<const int16_t>(_src);
const std::size_t nframes = src.size() / audio_format.channels;
size_t written = 0;
if (input_pos > SHINE_MAX_SAMPLES)
input_pos = 0;
/* write all data to de-interleaved buffers */
while (written < nframes) {
for (;
written < nframes && input_pos < frame_size;
written++, input_pos++) {
const size_t base =
written * audio_format.channels;
stereo[0][input_pos] = src[base];
stereo[1][input_pos] = src[base + 1];
}
/* write if chunk is filled */
WriteChunk(false);
}
}
void
ShineEncoder::Flush()
{
/* flush buffers and flush shine */
WriteChunk(true);
int written;
const auto data = (const std::byte *)shine_flush(shine, &written);
if (written > 0)
output_buffer.Append({data, std::size_t(written)});
}
const EncoderPlugin shine_encoder_plugin = {
"shine",
shine_encoder_init,
};

@ -1,9 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ENCODER_SHINE_HXX
#define MPD_ENCODER_SHINE_HXX
extern const struct EncoderPlugin shine_encoder_plugin;
#endif

@ -1,209 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "TwolameEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "pcm/AudioFormat.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "util/CNumberParser.hxx"
#include "util/SpanCast.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include <twolame.h>
#include <cassert>
#include <stdexcept>
class TwolameEncoder final : public Encoder {
twolame_options *options;
std::byte output_buffer[32768];
std::size_t fill = 0;
/**
* Call libtwolame's flush function when the output_buffer is
* empty?
*/
bool flush = false;
public:
static constexpr unsigned CHANNELS = 2;
explicit TwolameEncoder(twolame_options *_options) noexcept
:Encoder(false), options(_options) {}
~TwolameEncoder() noexcept override;
TwolameEncoder(const TwolameEncoder &) = delete;
TwolameEncoder &operator=(const TwolameEncoder &) = delete;
/* virtual methods from class Encoder */
void End() override {
flush = true;
}
void Flush() override {
flush = true;
}
void Write(std::span<const std::byte> src) override;
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override;
};
class PreparedTwolameEncoder final : public PreparedEncoder {
float quality;
int bitrate;
public:
explicit PreparedTwolameEncoder(const ConfigBlock &block);
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &audio_format) override;
[[nodiscard]] const char *GetMimeType() const noexcept override {
return "audio/mpeg";
}
};
static constexpr Domain twolame_encoder_domain("twolame_encoder");
PreparedTwolameEncoder::PreparedTwolameEncoder(const ConfigBlock &block)
{
const char *value;
char *endptr;
value = block.GetBlockValue("quality");
if (value != nullptr) {
/* a quality was configured (VBR) */
quality = float(ParseDouble(value, &endptr));
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
throw FmtRuntimeError("quality {:?} is not a number in the "
"range -1 to 10",
value);
if (block.GetBlockValue("bitrate") != nullptr)
throw std::runtime_error("quality and bitrate are both defined");
} else {
/* a bit rate was configured */
value = block.GetBlockValue("bitrate");
if (value == nullptr)
throw std::runtime_error("neither bitrate nor quality defined");
quality = -2.0;
bitrate = ParseInt(value, &endptr);
if (*endptr != '\0' || bitrate <= 0)
throw std::runtime_error("bitrate should be a positive integer");
}
}
static PreparedEncoder *
twolame_encoder_init(const ConfigBlock &block)
{
FmtDebug(twolame_encoder_domain,
"libtwolame version {}", get_twolame_version());
return new PreparedTwolameEncoder(block);
}
static void
twolame_encoder_setup(twolame_options *options, float quality, int bitrate,
const AudioFormat &audio_format)
{
if (quality >= -1.0f) {
/* a quality was configured (VBR) */
if (0 != twolame_set_VBR(options, true))
throw std::runtime_error("error setting twolame VBR mode");
if (0 != twolame_set_VBR_q(options, quality))
throw std::runtime_error("error setting twolame VBR quality");
} else {
/* a bit rate was configured */
if (0 != twolame_set_brate(options, bitrate))
throw std::runtime_error("error setting twolame bitrate");
}
if (0 != twolame_set_num_channels(options, audio_format.channels))
throw std::runtime_error("error setting twolame num channels");
if (0 != twolame_set_in_samplerate(options,
audio_format.sample_rate))
throw std::runtime_error("error setting twolame sample rate");
if (0 > twolame_init_params(options))
throw std::runtime_error("error initializing twolame params");
}
Encoder *
PreparedTwolameEncoder::Open(AudioFormat &audio_format)
{
audio_format.format = SampleFormat::S16;
audio_format.channels = TwolameEncoder::CHANNELS;
auto options = twolame_init();
if (options == nullptr)
throw std::runtime_error("twolame_init() failed");
try {
twolame_encoder_setup(options, quality, bitrate,
audio_format);
} catch (...) {
twolame_close(&options);
throw;
}
return new TwolameEncoder(options);
}
TwolameEncoder::~TwolameEncoder() noexcept
{
twolame_close(&options);
}
void
TwolameEncoder::Write(std::span<const std::byte> _src)
{
const auto src = FromBytesStrict<const int16_t>(_src);
assert(fill == 0);
const std::size_t num_frames = src.size() / CHANNELS;
int bytes_out = twolame_encode_buffer_interleaved(options,
src.data(), num_frames,
(unsigned char *)output_buffer,
sizeof(output_buffer));
if (bytes_out < 0)
throw std::runtime_error("twolame encoder failed");
fill = (std::size_t)bytes_out;
}
std::span<const std::byte>
TwolameEncoder::Read(std::span<std::byte>) noexcept
{
assert(fill <= sizeof(output_buffer));
if (fill == 0 && flush) {
int ret = twolame_encode_flush(options,
(unsigned char *)output_buffer,
sizeof(output_buffer));
if (ret > 0)
fill = (std::size_t)ret;
flush = false;
}
return std::span{output_buffer}.first(std::exchange(fill, 0));
}
const EncoderPlugin twolame_encoder_plugin = {
"twolame",
twolame_encoder_init,
};

@ -1,9 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ENCODER_TWOLAME_HXX
#define MPD_ENCODER_TWOLAME_HXX
extern const struct EncoderPlugin twolame_encoder_plugin;
#endif

@ -1,251 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "VorbisEncoderPlugin.hxx"
#include "OggEncoder.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "lib/xiph/VorbisComment.hxx"
#include "tag/Names.hxx"
#include "pcm/AudioFormat.hxx"
#include "config/Domain.hxx"
#include "util/StringUtil.hxx"
#include "util/CNumberParser.hxx"
#include <vorbis/vorbisenc.h>
class VorbisEncoder final : public OggEncoder {
AudioFormat audio_format;
vorbis_dsp_state vd;
vorbis_block vb;
vorbis_info vi;
public:
VorbisEncoder(float quality, int bitrate, AudioFormat &_audio_format);
~VorbisEncoder() noexcept override {
vorbis_block_clear(&vb);
vorbis_dsp_clear(&vd);
vorbis_info_clear(&vi);
}
VorbisEncoder(const VorbisEncoder &) = delete;
VorbisEncoder &operator=(const VorbisEncoder &) = delete;
/* virtual methods from class Encoder */
void End() override {
PreTag();
}
void PreTag() override;
void SendTag(const Tag &tag) override;
void Write(std::span<const std::byte> src) override;
private:
void HeaderOut(vorbis_comment &vc);
void SendHeader();
void BlockOut();
};
class PreparedVorbisEncoder final : public PreparedEncoder {
float quality = 3;
int bitrate;
public:
explicit PreparedVorbisEncoder(const ConfigBlock &block);
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &audio_format) override;
[[nodiscard]] const char *GetMimeType() const noexcept override {
return "audio/ogg";
}
};
PreparedVorbisEncoder::PreparedVorbisEncoder(const ConfigBlock &block)
{
const char *value = block.GetBlockValue("quality");
if (value != nullptr) {
/* a quality was configured (VBR) */
char *endptr;
quality = ParseDouble(value, &endptr);
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
throw FmtRuntimeError("quality {:?} is not a number in the "
"range -1 to 10",
value);
if (block.GetBlockValue("bitrate") != nullptr)
throw std::runtime_error("quality and bitrate are both defined");
} else {
/* a bit rate was configured */
value = block.GetBlockValue("bitrate");
if (value == nullptr)
return;
quality = -2.0;
char *endptr;
bitrate = ParseInt(value, &endptr);
if (*endptr != '\0' || bitrate <= 0)
throw std::runtime_error("bitrate should be a positive integer");
}
}
static PreparedEncoder *
vorbis_encoder_init(const ConfigBlock &block)
{
return new PreparedVorbisEncoder(block);
}
VorbisEncoder::VorbisEncoder(float quality, int bitrate,
AudioFormat &_audio_format)
:OggEncoder(true)
{
vorbis_info_init(&vi);
_audio_format.format = SampleFormat::FLOAT;
audio_format = _audio_format;
if (quality >= -1.0f) {
/* a quality was configured (VBR) */
if (0 != vorbis_encode_init_vbr(&vi,
audio_format.channels,
audio_format.sample_rate,
quality * 0.1f)) {
vorbis_info_clear(&vi);
throw std::runtime_error("error initializing vorbis vbr");
}
} else {
/* a bit rate was configured */
if (0 != vorbis_encode_init(&vi,
audio_format.channels,
audio_format.sample_rate, -1.0,
bitrate * 1000, -1.0f)) {
vorbis_info_clear(&vi);
throw std::runtime_error("error initializing vorbis encoder");
}
}
vorbis_analysis_init(&vd, &vi);
vorbis_block_init(&vd, &vb);
SendHeader();
}
void
VorbisEncoder::HeaderOut(vorbis_comment &vc)
{
ogg_packet packet, comments, codebooks;
vorbis_analysis_headerout(&vd, &vc,
&packet, &comments, &codebooks);
stream.PacketIn(packet);
stream.PacketIn(comments);
stream.PacketIn(codebooks);
}
void
VorbisEncoder::SendHeader()
{
VorbisComment vc;
HeaderOut(vc);
}
Encoder *
PreparedVorbisEncoder::Open(AudioFormat &audio_format)
{
return new VorbisEncoder(quality, bitrate, audio_format);
}
void
VorbisEncoder::BlockOut()
{
while (vorbis_analysis_blockout(&vd, &vb) == 1) {
vorbis_analysis(&vb, nullptr);
vorbis_bitrate_addblock(&vb);
ogg_packet packet;
while (vorbis_bitrate_flushpacket(&vd, &packet))
stream.PacketIn(packet);
}
}
void
VorbisEncoder::PreTag()
{
vorbis_analysis_wrote(&vd, 0);
BlockOut();
/* reinitialize vorbis_dsp_state and vorbis_block to reset the
end-of-stream marker */
vorbis_block_clear(&vb);
vorbis_dsp_clear(&vd);
vorbis_analysis_init(&vd, &vi);
vorbis_block_init(&vd, &vb);
Flush();
}
static void
copy_tag_to_vorbis_comment(VorbisComment &vc, const Tag &tag)
{
for (const auto &item : tag) {
char name[64];
ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
vc.AddTag(name, item.value);
}
}
void
VorbisEncoder::SendTag(const Tag &tag)
{
/* write the vorbis_comment object */
VorbisComment comment;
copy_tag_to_vorbis_comment(comment, tag);
/* reset ogg_stream_state and begin a new stream */
stream.Reinitialize(GenerateSerial());
/* send that vorbis_comment to the ogg_stream_state */
HeaderOut(comment);
}
static void
interleaved_to_vorbis_buffer(float **dest, const float *src,
std::size_t num_frames, std::size_t num_channels)
{
for (unsigned i = 0; i < num_frames; i++)
for (unsigned j = 0; j < num_channels; j++)
dest[j][i] = *src++;
}
void
VorbisEncoder::Write(std::span<const std::byte> src)
{
std::size_t num_frames = src.size() / audio_format.GetFrameSize();
/* this is for only 16-bit audio */
interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&vd, num_frames),
(const float *)(const void *)src.data(),
num_frames,
audio_format.channels);
vorbis_analysis_wrote(&vd, num_frames);
BlockOut();
}
const EncoderPlugin vorbis_encoder_plugin = {
"vorbis",
vorbis_encoder_init,
};

@ -1,9 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ENCODER_VORBIS_H
#define MPD_ENCODER_VORBIS_H
extern const struct EncoderPlugin vorbis_encoder_plugin;
#endif

@ -1,217 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Encoder support removed for mpd-dbcreate
#include "WaveEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "tag/RiffFormat.hxx"
#include "util/ByteOrder.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include <cassert>
#include <string.h>
class WaveEncoder final : public Encoder {
unsigned bits;
DynamicFifoBuffer<std::byte> buffer{8192};
public:
explicit WaveEncoder(AudioFormat &audio_format) noexcept;
/* virtual methods from class Encoder */
void Write(std::span<const std::byte> src) override;
std::span<const std::byte> Read(std::span<std::byte> b) noexcept override {
return b.first(buffer.Read(b.data(), b.size()));
}
};
class PreparedWaveEncoder final : public PreparedEncoder {
/* virtual methods from class PreparedEncoder */
Encoder *Open(AudioFormat &audio_format) override {
return new WaveEncoder(audio_format);
}
[[nodiscard]] const char *GetMimeType() const noexcept override {
return "audio/wav";
}
};
struct WaveHeader {
RiffFileHeader file_header;
RiffChunkHeader fmt_header;
RiffFmtChunk fmt;
RiffChunkHeader data_header;
};
static_assert(sizeof(WaveHeader) == 44);
static WaveHeader
MakeWaveHeader(int channels, int bits,
int freq, int block_size) noexcept
{
WaveHeader header{};
int data_size = 0x0FFFFFFF;
/* constants */
memcpy(header.file_header.id, "RIFF", 4);
memcpy(header.file_header.format, "WAVE", 4);
memcpy(header.fmt_header.id, "fmt ", 4);
memcpy(header.data_header.id, "data", 4);
/* wave format */
header.fmt.tag = ToLE16(RiffFmtChunk::TAG_PCM);
header.fmt.channels = ToLE16(channels);
header.fmt.bits_per_sample = ToLE16(bits);
header.fmt.sample_rate = ToLE32(freq);
header.fmt.block_align = ToLE16(block_size);
header.fmt.byte_rate = ToLE32(freq * block_size);
/* chunk sizes (fake data length) */
header.fmt_header.size = ToLE32(sizeof(header.fmt));
header.data_header.size = ToLE32(data_size);
header.file_header.size = ToLE32(4 +
sizeof(header.fmt_header) + sizeof(header.fmt) +
sizeof(header.data_header) + data_size);
return header;
}
static PreparedEncoder *
wave_encoder_init([[maybe_unused]] const ConfigBlock &block)
{
return new PreparedWaveEncoder();
}
WaveEncoder::WaveEncoder(AudioFormat &audio_format) noexcept
:Encoder(false)
{
assert(audio_format.IsValid());
switch (audio_format.format) {
case SampleFormat::S8:
bits = 8;
break;
case SampleFormat::S16:
bits = 16;
break;
case SampleFormat::S24_P32:
bits = 24;
break;
case SampleFormat::S32:
bits = 32;
break;
default:
audio_format.format = SampleFormat::S16;
bits = 16;
break;
}
auto range = buffer.Write();
assert(range.size() >= sizeof(WaveHeader));
auto *header = (WaveHeader *)(void *)range.data();
/* create PCM wave header in initial buffer */
*header = MakeWaveHeader(audio_format.channels,
bits,
audio_format.sample_rate,
(bits / 8) * audio_format.channels);
buffer.Append(sizeof(*header));
}
static size_t
pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
{
size_t cnt = length >> 1;
while (cnt > 0) {
*dst16++ = ToLE16(*src16++);
cnt--;
}
return length;
}
static size_t
pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) noexcept
{
size_t cnt = length >> 2;
while (cnt > 0){
*dst32++ = ToLE32(*src32++);
cnt--;
}
return length;
}
static size_t
pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) noexcept
{
uint32_t value;
uint8_t *dst_old = dst8;
length = length >> 2;
while (length > 0){
value = *src32++;
*dst8++ = (value) & 0xFF;
*dst8++ = (value >> 8) & 0xFF;
*dst8++ = (value >> 16) & 0xFF;
length--;
}
//correct buffer length
return (dst8 - dst_old);
}
void
WaveEncoder::Write(std::span<const std::byte> src)
{
std::size_t length = src.size();
std::byte *dst = buffer.Write(length);
if (IsLittleEndian()) {
switch (bits) {
case 8:
case 16:
case 32:// optimized cases
memcpy(dst, src.data(), length);
break;
case 24:
length = pcm24_to_wave((uint8_t *)dst,
(const uint32_t *)(const void *)src.data(),
length);
break;
}
} else {
switch (bits) {
case 8:
memcpy(dst, src.data(), length);
break;
case 16:
length = pcm16_to_wave((uint16_t *)dst,
(const uint16_t *)(const void *)src.data(),
length);
break;
case 24:
length = pcm24_to_wave((uint8_t *)dst,
(const uint32_t *)(const void *)src.data(),
length);
break;
case 32:
length = pcm32_to_wave((uint32_t *)dst,
(const uint32_t *)(const void *)src.data(),
length);
break;
}
}
buffer.Append(length);
}
/**
* Stub implementation - encoder support not needed for database creation
*/
const EncoderPlugin wave_encoder_plugin = {
"wave",
wave_encoder_init,
};

@ -1,9 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Encoder support removed for mpd-dbcreate
#ifndef MPD_ENCODER_WAVE_HXX
#define MPD_ENCODER_WAVE_HXX
#ifndef MPD_WAVE_ENCODER_PLUGIN_HXX
#define MPD_WAVE_ENCODER_PLUGIN_HXX
extern const struct EncoderPlugin wave_encoder_plugin;
#include "encoder/EncoderPlugin.hxx"
/**
* Stub implementation - encoder support not needed for database creation
*/
extern const EncoderPlugin wave_encoder_plugin;
#endif

@ -1,80 +0,0 @@
encoder_plugins_sources = [
'NullEncoderPlugin.cxx',
]
encoder_features.set('ENABLE_FLAC_ENCODER', flac_dep.found())
if flac_dep.found()
encoder_plugins_sources += 'FlacEncoderPlugin.cxx'
endif
if libopus_dep.found()
encoder_plugins_sources += 'OpusEncoderPlugin.cxx'
endif
encoder_features.set('ENABLE_VORBISENC', libvorbisenc_dep.found())
if libvorbisenc_dep.found()
encoder_plugins_sources += 'VorbisEncoderPlugin.cxx'
endif
if not get_option('lame').disabled()
# LAME doesn't have a pkg-config file so we have to use
# find_library()
liblame_dep = c_compiler.find_library('mp3lame', required: false)
if not liblame_dep.found()
# only if that was not found, use dependency() which may use the
# LAME subproject
liblame_dep = dependency('mp3lame', required: get_option('lame'))
endif
else
liblame_dep = dependency('', required: false)
endif
encoder_features.set('ENABLE_LAME', liblame_dep.found())
if liblame_dep.found()
encoder_plugins_sources += 'LameEncoderPlugin.cxx'
endif
libtwolame_dep = dependency('twolame', required: get_option('twolame'))
encoder_features.set('ENABLE_TWOLAME', libtwolame_dep.found())
if libtwolame_dep.found()
encoder_plugins_sources += 'TwolameEncoderPlugin.cxx'
endif
libshine_dep = dependency('shine', version: '>= 3.1', required: get_option('shine'))
encoder_features.set('ENABLE_SHINE', libshine_dep.found())
if libshine_dep.found()
encoder_plugins_sources += 'ShineEncoderPlugin.cxx'
endif
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
if get_option('wave_encoder') or need_wave_encoder
encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
endif
encoder_plugins = static_library(
'encoder_plugins',
encoder_plugins_sources,
include_directories: inc,
dependencies: [
pcm_basic_dep,
flac_dep,
ogg_dep,
libopus_dep,
libvorbisenc_dep,
libvorbis_dep,
liblame_dep,
libtwolame_dep,
libshine_dep,
log_dep,
],
)
encoder_plugins_dep = declare_dependency(
link_with: encoder_plugins,
dependencies: [
encoder_api_dep,
tag_dep,
pcm_dep,
config_dep,
],
)

@ -1,23 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "Factory.hxx"
#include "LoadOne.hxx"
#include "Prepared.hxx"
#include "config/Data.hxx"
#include "config/Block.hxx"
#include "lib/fmt/RuntimeError.hxx"
std::unique_ptr<PreparedFilter>
FilterFactory::MakeFilter(const char *name)
{
const auto *cfg = config.FindBlock(ConfigBlockOption::AUDIO_FILTER,
"name", name);
if (cfg == nullptr)
throw FmtRuntimeError("Filter template not found: {}",
name);
cfg->SetUsed();
return filter_configured_new(*cfg);
}

@ -1,22 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_FILTER_FACTORY_HXX
#define MPD_FILTER_FACTORY_HXX
#include <memory>
struct ConfigData;
class PreparedFilter;
/**
* Stub implementation - filter support not needed for database creation
*/
class FilterFactory {
const ConfigData &config;
public:
explicit FilterFactory(const ConfigData &_config) noexcept
:config(_config) {}
std::unique_ptr<PreparedFilter> MakeFilter(const char *name);
FilterFactory([[maybe_unused]] const ConfigData &config) {}
};
#endif

@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#include "Filter.hxx"
#include "pcm/AudioFormat.hxx"
const AudioFormat &
Filter::GetOutAudioFormat() const noexcept
{
// Return a static dummy audio format with minimal valid initialization
static const AudioFormat dummy_format{44100, SampleFormat::S16, 2};
return dummy_format;
}

@ -1,84 +1,42 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_FILTER_HXX
#define MPD_FILTER_HXX
#include "pcm/AudioFormat.hxx"
#include <cassert>
#include <cstddef>
#include <span>
#include <cstddef>
#include <memory>
class Filter {
protected:
AudioFormat out_audio_format;
explicit Filter(AudioFormat _out_audio_format) noexcept
:out_audio_format(_out_audio_format) {
assert(out_audio_format.IsValid());
}
struct AudioFormat;
/**
* Stub implementation - filter support not needed for database creation
*/
class Filter {
public:
virtual ~Filter() noexcept = default;
virtual ~Filter() = default;
/**
* Returns the #AudioFormat produced by FilterPCM().
*/
const AudioFormat &GetOutAudioFormat() const noexcept {
return out_audio_format;
virtual std::span<const std::byte> FilterPCM(std::span<const std::byte> src) {
return src; // Pass-through stub
}
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
virtual void Reset() noexcept {
virtual std::span<const std::byte> Flush() {
return {}; // Empty stub
}
/**
* Filters a block of PCM data.
*
* Throws on error.
*
* @param src the input buffer
* @return the output buffer (will be invalidated by deleting
* this object or any call to Reset(), FilterPCM(), ReadMore()
* or Flush()); may be empty if no output is currently
* available
*/
virtual std::span<const std::byte> FilterPCM(std::span<const std::byte> src) = 0;
/**
* Read more result data from the filter. After each
* FilterPCM() call, this should be called repeatedly until it
* returns an empty span.
*
* Throws on error.
*
* @return the output buffer (will be invalidated by deleting
* this object or any call to Reset(), FilterPCM(), ReadMore()
* or Flush()); may be empty if no output is currently
* available
*/
virtual std::span<const std::byte> ReadMore() {
return {};
return {}; // Empty stub
}
/**
* Flush pending data and return it. This should be called
* repeatedly until it returns an empty span.
*
* After calling this method, this object cannot be used again
* (not even Reset() is allowed).
*
* Throws on error.
*
* @return pending data (will be invalidated by deleting this
* object or by any call to Flush())
*/
virtual std::span<const std::byte> Flush() {
return {};
virtual void Reset() noexcept {
// No-op stub
}
virtual const AudioFormat &GetOutAudioFormat() const noexcept;
};
class PreparedFilter; // Forward declaration - see Prepared.hxx for full definition
#endif

@ -1,27 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
/** \file
*
* This header declares the filter_plugin class. It describes a
* plugin API for objects which filter raw PCM data.
*/
#ifndef MPD_FILTER_PLUGIN_HXX
#define MPD_FILTER_PLUGIN_HXX
#include <memory>
struct ConfigBlock;
class PreparedFilter;
struct FilterPlugin {
const char *name;
/**
* Allocates and configures a filter.
*/
std::unique_ptr<PreparedFilter> (*init)(const ConfigBlock &block);
};
#endif

@ -1,40 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "LoadChain.hxx"
#include "Factory.hxx"
#include "Prepared.hxx"
#include "plugins/AutoConvertFilterPlugin.hxx"
#include "plugins/TwoFilters.hxx"
#include "util/IterableSplitString.hxx"
#include <string>
static void
filter_chain_append_new(std::unique_ptr<PreparedFilter> &chain,
FilterFactory &factory,
std::string_view template_name)
{
/* using the AutoConvert filter just in case the specified
filter plugin does not support the exact input format */
chain = ChainFilters(std::move(chain),
/* unfortunately, MakeFilter() wants a
null-terminated string, so we need to
copy it here */
autoconvert_filter_new(factory.MakeFilter(std::string(template_name).c_str())),
template_name);
}
void
filter_chain_parse(std::unique_ptr<PreparedFilter> &chain,
FilterFactory &factory,
const char *spec)
{
for (const std::string_view i : IterableSplitString(spec, ',')) {
if (i.empty())
continue;
filter_chain_append_new(chain, factory, i);
}
}

@ -1,28 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_FILTER_LOAD_CHAIN_HXX
#define MPD_FILTER_LOAD_CHAIN_HXX
#include "Prepared.hxx"
#include <memory>
class FilterFactory;
class PreparedFilter;
struct ConfigBlock;
/**
* Builds a filter chain from a configuration string on the form
* "name1, name2, name3, ..." by looking up each name among the
* configured filter sections.
*
* Throws on error.
*
* @param chain the chain to append filters on
* @param config the global configuration to load filter definitions from
* @param spec the filter chain specification
* Stub implementation - filter support not needed for database creation
*/
void
filter_chain_parse(std::unique_ptr<PreparedFilter> &chain,
FilterFactory &factory,
const char *spec);
inline std::unique_ptr<PreparedFilter>
filter_chain_parse([[maybe_unused]] std::unique_ptr<PreparedFilter> &prepared,
[[maybe_unused]] FilterFactory &factory,
[[maybe_unused]] const char *spec)
{
return nullptr;
}
inline std::unique_ptr<PreparedFilter>
filter_chain_new([[maybe_unused]] const ConfigBlock *block)
{
return nullptr;
}
#endif

@ -1,24 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "LoadOne.hxx"
#include "FilterPlugin.hxx"
#include "Registry.hxx"
#include "Prepared.hxx"
#include "config/Block.hxx"
#include "lib/fmt/RuntimeError.hxx"
std::unique_ptr<PreparedFilter>
filter_configured_new(const ConfigBlock &block)
{
const char *plugin_name = block.GetBlockValue("plugin");
if (plugin_name == nullptr)
throw std::runtime_error("No filter plugin specified");
const auto *plugin = filter_plugin_by_name(plugin_name);
if (plugin == nullptr)
throw FmtRuntimeError("No such filter plugin: {}",
plugin_name);
return plugin->init(block);
}

@ -1,23 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_FILTER_LOAD_ONE_HXX
#define MPD_FILTER_LOAD_ONE_HXX
#include <memory>
struct ConfigBlock;
class PreparedFilter;
/**
* Creates a new filter, loads configuration and the plugin name from
* the specified configuration section.
*
* Throws on error.
*
* @param block the configuration section
*/
std::unique_ptr<PreparedFilter>
filter_configured_new(const ConfigBlock &block);
#endif

@ -1,18 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_NULL_FILTER_HXX
#define MPD_NULL_FILTER_HXX
#include "filter/Filter.hxx"
class NullFilter final : public Filter {
public:
explicit NullFilter(const AudioFormat &af):Filter(af) {}
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override {
return src;
}
};
#endif

@ -1,114 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "Observer.hxx"
#include "Filter.hxx"
#include "Prepared.hxx"
#include <cassert>
class FilterObserver::PreparedProxy final : public PreparedFilter {
FilterObserver &observer;
std::unique_ptr<PreparedFilter> prepared_filter;
Proxy *child = nullptr;
public:
PreparedProxy(FilterObserver &_observer,
std::unique_ptr<PreparedFilter> _prepared_filter) noexcept
:observer(_observer),
prepared_filter(std::move(_prepared_filter)) {}
~PreparedProxy() noexcept override {
assert(child == nullptr);
assert(observer.proxy == this);
observer.proxy = nullptr;
}
PreparedProxy(const PreparedProxy &) = delete;
PreparedProxy &operator=(const PreparedProxy &) = delete;
void Clear([[maybe_unused]] Proxy *_child) noexcept {
assert(child == _child);
child = nullptr;
}
Filter *Get() noexcept;
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
class FilterObserver::Proxy final : public Filter {
PreparedProxy &parent;
std::unique_ptr<Filter> filter;
public:
Proxy(PreparedProxy &_parent, std::unique_ptr<Filter> _filter) noexcept
:Filter(_filter->GetOutAudioFormat()),
parent(_parent), filter(std::move(_filter)) {}
~Proxy() noexcept override {
parent.Clear(this);
}
Proxy(const Proxy &) = delete;
Proxy &operator=(const Proxy &) = delete;
Filter *Get() noexcept {
return filter.get();
}
void Reset() noexcept override {
filter->Reset();
}
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override {
return filter->FilterPCM(src);
}
std::span<const std::byte> ReadMore() override {
return filter->ReadMore();
}
std::span<const std::byte> Flush() override {
return filter->Flush();
}
};
Filter *
FilterObserver::PreparedProxy::Get() noexcept
{
return child != nullptr
? child->Get()
: nullptr;
}
std::unique_ptr<Filter>
FilterObserver::PreparedProxy::Open(AudioFormat &af)
{
assert(child == nullptr);
auto c = std::make_unique<Proxy>(*this, prepared_filter->Open(af));
child = c.get();
return c;
}
std::unique_ptr<PreparedFilter>
FilterObserver::Set(std::unique_ptr<PreparedFilter> pf)
{
assert(proxy == nullptr);
auto p = std::make_unique<PreparedProxy>(*this, std::move(pf));
proxy = p.get();
return p;
}
Filter *
FilterObserver::Get() noexcept
{
return proxy != nullptr
? proxy->Get()
: nullptr;
}

@ -1,31 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_FILTER_OBSERVER_HXX
#define MPD_FILTER_OBSERVER_HXX
#include <memory>
class PreparedFilter;
class Filter;
class PreparedFilter;
/**
* A helper class which observes calls to a #PreparedFilter and allows
* the caller to access the #Filter instances created by it.
* Stub implementation - filter support not needed for database creation
*/
class FilterObserver {
class PreparedProxy;
class Proxy;
public:
FilterObserver() noexcept = default;
PreparedProxy *proxy = nullptr;
Filter *Get() noexcept { return nullptr; }
public:
/**
* @return a proxy object
*/
std::unique_ptr<PreparedFilter> Set(std::unique_ptr<PreparedFilter> pf);
void Set(Filter *) noexcept {}
Filter *Get() noexcept;
// Also accept PreparedFilter unique_ptr, return it for chaining
// Use template to avoid incomplete type issues
template<typename T>
std::unique_ptr<T> Set(std::unique_ptr<T> p) noexcept {
return p;
}
};
#endif

@ -1,28 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_PREPARED_FILTER_HXX
#define MPD_PREPARED_FILTER_HXX
#ifndef MPD_FILTER_PREPARED_HXX
#define MPD_FILTER_PREPARED_HXX
#include "Filter.hxx"
#include <memory>
struct AudioFormat;
class Filter;
/**
* Stub implementation - filter support not needed for database creation
*/
class PreparedFilter {
public:
virtual ~PreparedFilter() = default;
/**
* Opens the filter, preparing it for FilterPCM().
*
* Throws on error.
*
* @param af the audio format of incoming data; the
* plugin may modify the object to enforce another input
* format
*/
virtual std::unique_ptr<Filter> Open(AudioFormat &af) = 0;
virtual std::unique_ptr<Filter> Open([[maybe_unused]] const AudioFormat &af) {
return nullptr; // Stub returns nullptr
}
};
#endif

@ -1,34 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "Registry.hxx"
#include "FilterPlugin.hxx"
#include "plugins/NullFilterPlugin.hxx"
#include "plugins/RouteFilterPlugin.hxx"
#include "plugins/NormalizeFilterPlugin.hxx"
#include "plugins/FfmpegFilterPlugin.hxx"
#include "plugins/HdcdFilterPlugin.hxx"
#include "config.h"
#include <string.h>
static constinit const FilterPlugin *const filter_plugins[] = {
&null_filter_plugin,
&route_filter_plugin,
&normalize_filter_plugin,
#ifdef HAVE_LIBAVFILTER
&ffmpeg_filter_plugin,
&hdcd_filter_plugin,
#endif
nullptr,
};
const FilterPlugin *
filter_plugin_by_name(const char *name) noexcept
{
for (unsigned i = 0; filter_plugins[i] != nullptr; ++i)
if (strcmp(filter_plugins[i]->name, name) == 0)
return filter_plugins[i];
return nullptr;
}

@ -1,19 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
/** \file
*
* This library manages all filter plugins which are enabled at
* compile time.
*/
#ifndef MPD_FILTER_REGISTRY_HXX
#define MPD_FILTER_REGISTRY_HXX
struct FilterPlugin;
[[gnu::pure]]
const FilterPlugin *
filter_plugin_by_name(const char *name) noexcept;
#endif

@ -1,6 +1,9 @@
# STUB FILE - Filter support removed for mpd-dbcreate
# Minimal build file for stub implementations
filter_api = static_library(
'filter_api',
'Observer.cxx',
'Filter.cxx',
include_directories: inc,
)
@ -8,23 +11,12 @@ filter_api_dep = declare_dependency(
link_with: filter_api,
)
subdir('plugins')
filter_glue = static_library(
'filter_glue',
'Registry.cxx',
'Factory.cxx',
'LoadOne.cxx',
'LoadChain.cxx',
include_directories: inc,
dependencies: [
fmt_dep,
],
)
# Empty stub library for plugin dependencies
filter_plugins_dep = declare_dependency()
# Empty stub library for filter glue dependencies
filter_glue_dep = declare_dependency(
link_with: filter_glue,
dependencies: [
filter_plugins_dep,
filter_api_dep,
],
)

@ -1,56 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "AutoConvertFilterPlugin.hxx"
#include "ConvertFilterPlugin.hxx"
#include "TwoFilters.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include "pcm/AudioFormat.hxx"
#include <cassert>
#include <memory>
class PreparedAutoConvertFilter final : public PreparedFilter {
/**
* The underlying filter.
*/
std::unique_ptr<PreparedFilter> filter;
public:
explicit PreparedAutoConvertFilter(std::unique_ptr<PreparedFilter> _filter) noexcept
:filter(std::move(_filter)) {}
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
std::unique_ptr<Filter>
PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format)
{
assert(in_audio_format.IsValid());
/* open the "real" filter */
AudioFormat child_audio_format = in_audio_format;
auto new_filter = filter->Open(child_audio_format);
/* need to convert? */
if (in_audio_format == child_audio_format)
/* no */
return new_filter;
/* yes - create a convert_filter */
auto convert = convert_filter_new(in_audio_format,
child_audio_format);
return std::make_unique<TwoFilters>(std::move(convert),
std::move(new_filter));
}
std::unique_ptr<PreparedFilter>
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept
{
return std::make_unique<PreparedAutoConvertFilter>(std::move(filter));
}

@ -1,20 +1,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
#ifndef MPD_AUTO_CONVERT_FILTER_PLUGIN_HXX
#define MPD_AUTO_CONVERT_FILTER_PLUGIN_HXX
#include "filter/Prepared.hxx"
#include <memory>
class PreparedFilter;
/**
* Creates a new "autoconvert" filter. When opened, it ensures that
* the input audio format isn't changed. If the underlying filter
* requests a different format, it automatically creates a
* convert_filter.
* Stub implementation - filter support not needed for database creation
*/
std::unique_ptr<PreparedFilter>
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept;
inline std::unique_ptr<PreparedFilter>
autoconvert_filter_new([[maybe_unused]] std::unique_ptr<PreparedFilter> a = nullptr)
{
return nullptr;
}
#endif

@ -1,118 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "ConvertFilterPlugin.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include "pcm/AudioFormat.hxx"
#include "pcm/Convert.hxx"
#include <cassert>
#include <memory>
class ConvertFilter final : public Filter {
/**
* The input audio format; PCM data is passed to the filter()
* method in this format.
*/
const AudioFormat in_audio_format;
/**
* This object is only "open" if #in_audio_format !=
* #out_audio_format.
*/
std::unique_ptr<PcmConvert> state;
public:
explicit ConvertFilter(const AudioFormat &audio_format);
void Set(const AudioFormat &_out_audio_format);
void Reset() noexcept override {
if (state)
state->Reset();
}
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
std::span<const std::byte> Flush() override {
return state
? state->Flush()
: std::span<const std::byte>{};
}
};
class PreparedConvertFilter final : public PreparedFilter {
public:
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
void
ConvertFilter::Set(const AudioFormat &_out_audio_format)
{
assert(_out_audio_format.IsValid());
if (_out_audio_format == out_audio_format)
/* no change */
return;
if (state) {
out_audio_format = in_audio_format;
state.reset();
}
if (_out_audio_format == in_audio_format)
/* optimized special case: no-op */
return;
state = std::make_unique<PcmConvert>(in_audio_format,
_out_audio_format);
out_audio_format = _out_audio_format;
}
ConvertFilter::ConvertFilter(const AudioFormat &audio_format)
:Filter(audio_format), in_audio_format(audio_format)
{
assert(in_audio_format.IsValid());
}
std::unique_ptr<Filter>
PreparedConvertFilter::Open(AudioFormat &audio_format)
{
assert(audio_format.IsValid());
return std::make_unique<ConvertFilter>(audio_format);
}
std::span<const std::byte>
ConvertFilter::FilterPCM(std::span<const std::byte> src)
{
return state
? state->Convert(src)
/* optimized special case: no-op */
: std::span<const std::byte>{src};
}
std::unique_ptr<PreparedFilter>
convert_filter_prepare() noexcept
{
return std::make_unique<PreparedConvertFilter>();
}
std::unique_ptr<Filter>
convert_filter_new(const AudioFormat in_audio_format,
const AudioFormat out_audio_format)
{
auto filter = std::make_unique<ConvertFilter>(in_audio_format);
filter->Set(out_audio_format);
return filter;
}
void
convert_filter_set(Filter *_filter, AudioFormat out_audio_format)
{
auto *filter = (ConvertFilter *)_filter;
filter->Set(out_audio_format);
}

@ -1,31 +1,37 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX
#define MPD_CONVERT_FILTER_PLUGIN_HXX
#include "filter/Prepared.hxx"
#include <memory>
class PreparedFilter;
class Filter;
struct AudioFormat;
std::unique_ptr<PreparedFilter>
convert_filter_prepare() noexcept;
std::unique_ptr<Filter>
convert_filter_new(AudioFormat in_audio_format,
AudioFormat out_audio_format);
class Filter;
/**
* Sets the output audio format for the specified filter. You must
* call this after the filter has been opened. Since this audio
* format switch is a violation of the filter API, this filter must be
* the last in a chain.
*
* Throws on error.
* Stub implementation - filter support not needed for database creation
*/
void
convert_filter_set(Filter *filter, AudioFormat out_audio_format);
inline std::unique_ptr<PreparedFilter>
convert_filter_new([[maybe_unused]] const AudioFormat &in,
[[maybe_unused]] const AudioFormat &out)
{
return nullptr;
}
inline void
convert_filter_set([[maybe_unused]] Filter *,
[[maybe_unused]] const AudioFormat &) noexcept
{
// No-op stub
}
inline std::unique_ptr<PreparedFilter>
convert_filter_prepare()
{
return nullptr;
}
#endif

@ -1,104 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "FfmpegFilter.hxx"
#include "lib/ffmpeg/Interleave.hxx"
#include "lib/ffmpeg/SampleFormat.hxx"
extern "C" {
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
}
#include <string.h>
FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
const AudioFormat &_out_audio_format,
Ffmpeg::FilterGraph &&_graph,
AVFilterContext &_buffer_src,
AVFilterContext &_buffer_sink) noexcept
:Filter(_out_audio_format),
graph(std::move(_graph)),
buffer_src(_buffer_src),
buffer_sink(_buffer_sink),
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
in_sample_rate(in_audio_format.sample_rate),
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
in_channels(in_audio_format.channels),
#endif
in_audio_frame_size(in_audio_format.GetFrameSize()),
out_audio_frame_size(_out_audio_format.GetFrameSize())
{
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
#endif
}
inline std::span<const std::byte>
FfmpegFilter::ReadOutput()
{
frame.Unref();
if (int err = av_buffersink_get_frame(&buffer_sink, frame.get()); err < 0) {
if (err == AVERROR(EAGAIN) || err == AVERROR_EOF)
return {};
throw MakeFfmpegError(err, "av_buffersink_get_frame() failed");
}
return Ffmpeg::InterleaveFrame(*frame, interleave_buffer);
}
std::span<const std::byte>
FfmpegFilter::FilterPCM(std::span<const std::byte> src)
{
assert(!flushed);
/* submit source data into the FFmpeg audio buffer source */
frame.Unref();
frame->format = in_format;
frame->sample_rate = in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
frame->ch_layout = in_ch_layout;
#else
frame->channels = in_channels;
#endif
frame->nb_samples = src.size() / in_audio_frame_size;
frame->pts = pts;
pts += frame->nb_samples;
frame.GetBuffer();
memcpy(frame.GetData(0), src.data(), src.size());
if (int err = av_buffersrc_add_frame(&buffer_src, frame.get()); err < 0)
throw MakeFfmpegError(err, "av_buffersrc_write_frame() failed");
/* collect filtered data from the FFmpeg audio buffer sink */
/* TODO: call av_buffersink_get_frame() repeatedly? Not
possible with MPD's current Filter API */
return ReadOutput();
}
std::span<const std::byte>
FfmpegFilter::ReadMore()
{
return ReadOutput();
}
std::span<const std::byte>
FfmpegFilter::Flush()
{
if (!flushed) {
if (int err = av_buffersrc_add_frame(&buffer_src, nullptr); err < 0)
throw MakeFfmpegError(err, "av_buffersrc_write_frame() failed");
flushed = true;
}
return ReadOutput();
}

@ -1,62 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#pragma once
#include "filter/Filter.hxx"
#include "lib/ffmpeg/Buffer.hxx"
#include "lib/ffmpeg/Filter.hxx"
#include "lib/ffmpeg/Frame.hxx"
#include <cstdint>
/**
* A #Filter implementation using FFmpeg's libavfilter.
*/
class FfmpegFilter final : public Filter {
Ffmpeg::FilterGraph graph;
AVFilterContext &buffer_src, &buffer_sink;
Ffmpeg::Frame frame;
FfmpegBuffer interleave_buffer;
const int in_format, in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
AVChannelLayout in_ch_layout;
#else
const int in_channels;
#endif
const size_t in_audio_frame_size;
const size_t out_audio_frame_size;
/**
* Presentation timestamp. A counter for `AVFrame::pts`.
*/
int_least64_t pts = 0;
bool flushed = false;
public:
/**
* @param _graph a checked and configured AVFilterGraph
* @param _buffer_src an "abuffer" filter which serves as
* input
* @param _buffer_sink an "abuffersink" filter which serves as
* output
*/
FfmpegFilter(const AudioFormat &in_audio_format,
const AudioFormat &_out_audio_format,
Ffmpeg::FilterGraph &&_graph,
AVFilterContext &_buffer_src,
AVFilterContext &_buffer_sink) noexcept;
/* virtual methods from class Filter */
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
std::span<const std::byte> ReadMore() override;
std::span<const std::byte> Flush() override;
private:
std::span<const std::byte> ReadOutput();
};

@ -1,114 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "FfmpegFilterPlugin.hxx"
#include "FfmpegFilter.hxx"
#include "filter/FilterPlugin.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include "lib/ffmpeg/Filter.hxx"
#include "lib/ffmpeg/DetectFilterFormat.hxx"
#include "config/Block.hxx"
class PreparedFfmpegFilter final : public PreparedFilter {
const char *const graph_string;
public:
explicit PreparedFfmpegFilter(const char *_graph) noexcept
:graph_string(_graph) {}
/* virtual methods from class PreparedFilter */
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
/**
* Fallback for PreparedFfmpegFilter::Open() just in case the filter's
* native output format could not be determined.
*
* TODO: improve the MPD filter API to allow returning the output
* format later, and eliminate this kludge
*/
static auto
OpenWithAformat(const char *graph_string, AudioFormat &in_audio_format)
{
Ffmpeg::FilterGraph graph;
auto &buffer_src =
Ffmpeg::MakeAudioBufferSource(in_audio_format, *graph);
auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph);
AudioFormat out_audio_format = in_audio_format;
auto &aformat = Ffmpeg::MakeAformat(out_audio_format, *graph);
if (int error = avfilter_link(&aformat, 0, &buffer_sink, 0); error < 0)
throw MakeFfmpegError(error, "avfilter_link() failed");
graph.ParseSingleInOut(graph_string, aformat, buffer_src);
graph.CheckAndConfigure();
return std::make_unique<FfmpegFilter>(in_audio_format,
out_audio_format,
std::move(graph),
buffer_src,
buffer_sink);
}
std::unique_ptr<Filter>
PreparedFfmpegFilter::Open(AudioFormat &in_audio_format)
{
Ffmpeg::FilterGraph graph;
auto &buffer_src =
Ffmpeg::MakeAudioBufferSource(in_audio_format, *graph);
auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph);
/* if the filter's output format is not supported by MPD, this
"aformat" filter is inserted at the end and takes care for
the required conversion */
auto &aformat = Ffmpeg::MakeAutoAformat(*graph);
if (int error = avfilter_link(&aformat, 0, &buffer_sink, 0); error < 0)
throw MakeFfmpegError(error, "avfilter_link() failed");
graph.ParseSingleInOut(graph_string, aformat, buffer_src);
graph.CheckAndConfigure();
const auto out_audio_format =
Ffmpeg::DetectFilterOutputFormat(in_audio_format, buffer_src,
buffer_sink);
if (!out_audio_format.IsDefined())
/* the filter's native output format could not be
determined yet, but we need to know it now; as a
workaround for this MPD API deficiency, try again
with an "aformat" filter which forces a specific
output format */
return OpenWithAformat(graph_string, in_audio_format);
return std::make_unique<FfmpegFilter>(in_audio_format,
out_audio_format,
std::move(graph),
buffer_src,
buffer_sink);
}
static std::unique_ptr<PreparedFilter>
ffmpeg_filter_init(const ConfigBlock &block)
{
const char *graph = block.GetBlockValue("graph");
if (graph == nullptr)
throw std::runtime_error("Missing \"graph\" configuration");
/* check if the graph can be parsed (and discard the
object) */
Ffmpeg::FilterGraph().Parse(graph);
return std::make_unique<PreparedFfmpegFilter>(graph);
}
const FilterPlugin ffmpeg_filter_plugin = {
"ffmpeg",
ffmpeg_filter_init,
};

@ -1,11 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_FFMPEG_FILTER_PLUGIN_HXX
#define MPD_FFMPEG_FILTER_PLUGIN_HXX
struct FilterPlugin;
extern const FilterPlugin ffmpeg_filter_plugin;
#endif

@ -1,79 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "HdcdFilterPlugin.hxx"
#include "FfmpegFilter.hxx"
#include "filter/FilterPlugin.hxx"
#include "filter/Filter.hxx"
#include "filter/NullFilter.hxx"
#include "filter/Prepared.hxx"
#include "lib/ffmpeg/Filter.hxx"
#include "pcm/AudioFormat.hxx"
static constexpr const char *hdcd_graph = "hdcd";
[[gnu::pure]]
static bool
MaybeHdcd(const AudioFormat &audio_format) noexcept
{
return audio_format.sample_rate == 44100 &&
audio_format.format == SampleFormat::S16 &&
audio_format.channels == 2;
}
static auto
OpenHdcdFilter(AudioFormat &in_audio_format)
{
Ffmpeg::FilterGraph graph;
auto &buffer_src =
Ffmpeg::MakeAudioBufferSource(in_audio_format,
*graph);
auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph);
graph.ParseSingleInOut(hdcd_graph, buffer_sink, buffer_src);
graph.CheckAndConfigure();
auto out_audio_format = in_audio_format;
// TODO: convert to 32 bit only if HDCD actually detected
out_audio_format.format = SampleFormat::S32;
return std::make_unique<FfmpegFilter>(in_audio_format,
out_audio_format,
std::move(graph),
buffer_src,
buffer_sink);
}
class PreparedHdcdFilter final : public PreparedFilter {
public:
/* virtual methods from class PreparedFilter */
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
std::unique_ptr<Filter>
PreparedHdcdFilter::Open(AudioFormat &audio_format)
{
if (MaybeHdcd(audio_format))
return OpenHdcdFilter(audio_format);
else
/* this cannot be HDCD, so let's copy as-is using
NullFilter */
return std::make_unique<NullFilter>(audio_format);
}
static std::unique_ptr<PreparedFilter>
hdcd_filter_init(const ConfigBlock &)
{
/* check if the graph can be parsed (and discard the
object) */
Ffmpeg::FilterGraph().Parse(hdcd_graph);
return std::make_unique<PreparedHdcdFilter>();
}
const FilterPlugin hdcd_filter_plugin = {
"hdcd",
hdcd_filter_init,
};

@ -1,11 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_HDCD_FILTER_PLUGIN_HXX
#define MPD_HDCD_FILTER_PLUGIN_HXX
struct FilterPlugin;
extern const FilterPlugin hdcd_filter_plugin;
#endif

@ -1,73 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "NormalizeFilterPlugin.hxx"
#include "filter/FilterPlugin.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include "pcm/Buffer.hxx"
#include "pcm/AudioFormat.hxx"
#include "pcm/Normalizer.hxx"
#include "util/SpanCast.hxx"
class NormalizeFilter final : public Filter {
PcmNormalizer normalizer;
PcmBuffer buffer;
public:
explicit NormalizeFilter(const AudioFormat &audio_format)
:Filter(audio_format) {
}
NormalizeFilter(const NormalizeFilter &) = delete;
NormalizeFilter &operator=(const NormalizeFilter &) = delete;
/* virtual methods from class Filter */
void Reset() noexcept override {
normalizer.Reset();
}
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
};
class PreparedNormalizeFilter final : public PreparedFilter {
public:
/* virtual methods from class PreparedFilter */
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
static std::unique_ptr<PreparedFilter>
normalize_filter_init([[maybe_unused]] const ConfigBlock &block)
{
return std::make_unique<PreparedNormalizeFilter>();
}
std::unique_ptr<Filter>
PreparedNormalizeFilter::Open(AudioFormat &audio_format)
{
audio_format.format = SampleFormat::S16;
return std::make_unique<NormalizeFilter>(audio_format);
}
std::span<const std::byte>
NormalizeFilter::FilterPCM(std::span<const std::byte> _src)
{
const auto src = FromBytesStrict<const int16_t>(_src);
auto *dest = (int16_t *)buffer.GetT<int16_t>(src.size());
normalizer.ProcessS16(dest, src);
return std::as_bytes(std::span{dest, src.size()});
}
const FilterPlugin normalize_filter_plugin = {
"normalize",
normalize_filter_init,
};
std::unique_ptr<PreparedFilter>
normalize_filter_prepare() noexcept
{
return std::make_unique<PreparedNormalizeFilter>();
}

@ -1,17 +1,22 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_NORMALIZE_FILTER_PLUGIN_HXX
#define MPD_NORMALIZE_FILTER_PLUGIN_HXX
#include "filter/Prepared.hxx"
#include <memory>
struct FilterPlugin;
class PreparedFilter;
struct ConfigBlock;
extern const FilterPlugin normalize_filter_plugin;
std::unique_ptr<PreparedFilter>
normalize_filter_prepare() noexcept;
/**
* Stub implementation - filter support not needed for database creation
*/
inline std::unique_ptr<PreparedFilter>
normalize_filter_prepare([[maybe_unused]] const ConfigBlock *block = nullptr)
{
return nullptr;
}
#endif

@ -1,32 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
/** \file
*
* This filter plugin does nothing. That is not quite useful, except
* for testing the filter core, or as a template for new filter
* plugins.
*/
#include "NullFilterPlugin.hxx"
#include "filter/FilterPlugin.hxx"
#include "filter/NullFilter.hxx"
#include "filter/Prepared.hxx"
class PreparedNullFilter final : public PreparedFilter {
public:
std::unique_ptr<Filter> Open(AudioFormat &af) override {
return std::make_unique<NullFilter>(af);
}
};
static std::unique_ptr<PreparedFilter>
null_filter_init([[maybe_unused]] const ConfigBlock &block)
{
return std::make_unique<PreparedNullFilter>();
}
const FilterPlugin null_filter_plugin = {
"null",
null_filter_init,
};

@ -1,11 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_NULL_FILTER_PLUGIN_HXX
#define MPD_NULL_FILTER_PLUGIN_HXX
struct FilterPlugin;
extern const FilterPlugin null_filter_plugin;
#endif

@ -1,217 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "ReplayGainFilterPlugin.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include "tag/ReplayGainInfo.hxx"
#include "config/ReplayGainConfig.hxx"
#include "mixer/Control.hxx"
#include "mixer/Mixer.hxx"
#include "mixer/Listener.hxx"
#include "pcm/AudioFormat.hxx"
#include "pcm/Volume.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include <cassert>
#include <exception>
static constexpr Domain replay_gain_domain("replay_gain");
class ReplayGainFilter final : public Filter {
const ReplayGainConfig config;
/**
* If set, then this hardware mixer is used for applying
* replay gain, instead of the software volume library.
*/
Mixer *const mixer;
/**
* The base volume level for scale=1.0, between 1 and 100
* (including).
*/
const unsigned base;
ReplayGainMode mode = ReplayGainMode::OFF;
ReplayGainInfo info;
/**
* About the current volume: it is between 0 and a value that
* may or may not exceed #PCM_VOLUME_1.
*
* If the default value of true is used for replaygain_limit, the
* application of the volume to the signal will never cause clipping.
*
* On the other hand, if the user has set replaygain_limit to false,
* the chance of clipping is explicitly preferred if that's required to
* maintain a consistent audio level. Whether clipping will actually
* occur depends on what value the user is using for replaygain_preamp.
*/
PcmVolume pv;
public:
ReplayGainFilter(const ReplayGainConfig &_config, bool allow_convert,
const AudioFormat &audio_format,
Mixer *_mixer, unsigned _base)
:Filter(audio_format),
config(_config),
mixer(_mixer), base(_base) {
info.Clear();
out_audio_format.format = pv.Open(out_audio_format.format,
allow_convert);
}
void SetInfo(const ReplayGainInfo *_info) {
if (_info != nullptr)
info = *_info;
else
info.Clear();
Update();
}
void SetMode(ReplayGainMode _mode) {
if (_mode == mode)
/* no change */
return;
FmtDebug(replay_gain_domain,
"replay gain mode has changed {}->{}",
ToString(mode), ToString(_mode));
mode = _mode;
Update();
}
/**
* Recalculates the new volume after a property was changed.
*/
void Update();
/* virtual methods from class Filter */
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
};
class PreparedReplayGainFilter final : public PreparedFilter {
const ReplayGainConfig config;
/**
* If set, then this hardware mixer is used for applying
* replay gain, instead of the software volume library.
*/
Mixer *mixer = nullptr;
/**
* Allow the class to convert to a different #SampleFormat to
* preserve quality?
*/
const bool allow_convert;
/**
* The base volume level for scale=1.0, between 1 and 100
* (including).
*/
unsigned base;
public:
explicit PreparedReplayGainFilter(const ReplayGainConfig _config,
bool _allow_convert)
:config(_config), allow_convert(_allow_convert) {}
void SetMixer(Mixer *_mixer, unsigned _base) {
assert(_mixer == nullptr || (_base > 0 && _base <= 100));
mixer = _mixer;
base = _base;
}
/* virtual methods from class Filter */
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
void
ReplayGainFilter::Update()
{
unsigned volume = PCM_VOLUME_1;
if (mode != ReplayGainMode::OFF) {
const auto &tuple = info.Get(mode);
float scale = tuple.CalculateScale(config);
FmtDebug(replay_gain_domain, "scale={}\n", scale);
volume = pcm_float_to_volume(scale);
}
if (mixer != nullptr) {
/* update the hardware mixer volume */
unsigned _volume = (volume * base) / PCM_VOLUME_1;
if (_volume > 100)
_volume = 100;
try {
mixer->LockSetVolume(_volume);
/* invoke the mixer's listener manually, just
in case the mixer implementation didn't do
that already (this depends on the
implementation) */
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
} catch (...) {
LogError(std::current_exception(),
"Failed to update hardware mixer");
}
} else
pv.SetVolume(volume);
}
std::unique_ptr<PreparedFilter>
NewReplayGainFilter(const ReplayGainConfig &config,
bool allow_convert) noexcept
{
return std::make_unique<PreparedReplayGainFilter>(config,
allow_convert);
}
std::unique_ptr<Filter>
PreparedReplayGainFilter::Open(AudioFormat &af)
{
return std::make_unique<ReplayGainFilter>(config, allow_convert,
af, mixer, base);
}
std::span<const std::byte>
ReplayGainFilter::FilterPCM(std::span<const std::byte> src)
{
return mixer != nullptr
? std::span<const std::byte>{src}
: pv.Apply(src);
}
void
replay_gain_filter_set_mixer(PreparedFilter &_filter, Mixer *mixer,
unsigned base)
{
auto &filter = (PreparedReplayGainFilter &)_filter;
filter.SetMixer(mixer, base);
}
void
replay_gain_filter_set_info(Filter &_filter, const ReplayGainInfo *info)
{
auto &filter = (ReplayGainFilter &)_filter;
filter.SetInfo(info);
}
void
replay_gain_filter_set_mode(Filter &_filter, ReplayGainMode mode)
{
auto &filter = (ReplayGainFilter &)_filter;
filter.SetMode(mode);
}

@ -1,49 +1,47 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
#include "ReplayGainMode.hxx"
#include "filter/Prepared.hxx"
#include <memory>
#include <cstdint>
class Filter;
class PreparedFilter;
class Mixer;
struct ReplayGainConfig;
struct ReplayGainInfo;
/**
* @param allow_convert allow the class to convert to a different
* #SampleFormat to preserve quality?
*/
std::unique_ptr<PreparedFilter>
NewReplayGainFilter(const ReplayGainConfig &config,
bool allow_convert) noexcept;
enum class ReplayGainMode : uint8_t;
/**
* Enables or disables the hardware mixer for applying replay gain.
*
* @param mixer the hardware mixer, or nullptr to fall back to software
* volume
* @param base the base volume level for scale=1.0, between 1 and 100
* (including).
* Stub implementation - filter support not needed for database creation
*/
void
replay_gain_filter_set_mixer(PreparedFilter &_filter, Mixer *mixer,
unsigned base);
/**
* Sets a new #ReplayGainInfo at the beginning of a new song.
*
* @param info the new #ReplayGainInfo value, or nullptr if no replay
* gain data is available for the current song
*/
void
replay_gain_filter_set_info(Filter &filter, const ReplayGainInfo *info);
void
replay_gain_filter_set_mode(Filter &filter, ReplayGainMode mode);
inline std::unique_ptr<PreparedFilter>
NewReplayGainFilter([[maybe_unused]] const ReplayGainConfig &config,
[[maybe_unused]] bool allow_convert = false)
{
return nullptr;
}
inline void
replay_gain_filter_set_mode([[maybe_unused]] Filter &,
[[maybe_unused]] ReplayGainMode) noexcept
{
// No-op stub
}
inline void
replay_gain_filter_set_info([[maybe_unused]] Filter &,
[[maybe_unused]] const void *) noexcept
{
// No-op stub
}
inline void
replay_gain_filter_set_mixer([[maybe_unused]] PreparedFilter &,
[[maybe_unused]] class Mixer *,
[[maybe_unused]] unsigned) noexcept
{
// No-op stub
}
#endif

@ -1,260 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
/** \file
*
* This filter copies audio data between channels. Useful for
* upmixing mono/stereo audio to surround speaker configurations.
*
* Its configuration consists of a "filter" section with a single
* "routes" entry, formatted as: \\
* routes "0>1, 1>0, 2>2, 3>3, 3>4" \\
* where each pair of numbers signifies a set of channels.
* Each source>dest pair leads to the data from channel #source
* being copied to channel #dest in the output.
*
* Example: \\
* routes "0>0, 1>1, 0>2, 1>3"\\
* upmixes stereo audio to a 4-speaker system, copying the front-left
* (0) to front left (0) and rear left (2), copying front-right (1) to
* front-right (1) and rear-right (3).
*
* If multiple sources are copied to the same destination channel, only
* one of them takes effect.
*/
#include "RouteFilterPlugin.hxx"
#include "config/Block.hxx"
#include "filter/FilterPlugin.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include "pcm/AudioFormat.hxx"
#include "pcm/Buffer.hxx"
#include "pcm/Silence.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "util/StringStrip.hxx"
#include <array>
#include <cstdint>
#include <stdexcept>
#include <string.h>
#include <stdlib.h>
class RouteFilter final : public Filter {
/**
* The set of copy operations to perform on each sample
* The index is an output channel to use, the value is
* a corresponding input channel from which to take the
* data. A -1 means "no source"
*/
const std::array<int8_t, MAX_CHANNELS> sources;
/**
* The actual input format of our signal, once opened
*/
const AudioFormat input_format;
/**
* The size, in bytes, of each multichannel frame in the
* input buffer
*/
const size_t input_frame_size;
/**
* The size, in bytes, of each multichannel frame in the
* output buffer
*/
size_t output_frame_size;
/**
* The output buffer used last time around, can be reused if the size doesn't differ.
*/
PcmBuffer output_buffer;
public:
RouteFilter(const AudioFormat &audio_format, unsigned out_channels,
const std::array<int8_t, MAX_CHANNELS> &_sources);
/* virtual methods from class Filter */
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
};
class PreparedRouteFilter final : public PreparedFilter {
/**
* The minimum number of channels we need for output
* to be able to perform all the copies the user has specified
*/
unsigned min_output_channels;
/**
* The minimum number of input channels we need to
* copy all the data the user has requested. If fewer
* than this many are supplied by the input, undefined
* copy operations are given zeroed sources in stead.
*/
unsigned min_input_channels;
/**
* The set of copy operations to perform on each sample
* The index is an output channel to use, the value is
* a corresponding input channel from which to take the
* data. A -1 means "no source"
*/
std::array<int8_t, MAX_CHANNELS> sources;
public:
/**
* Parse the "routes" section, a string on the form
* a>b, c>d, e>f, ...
* where a... are non-unique, non-negative integers
* and input channel a gets copied to output channel b, etc.
* @param block the configuration block to read
* @param filter a route_filter whose min_channels and sources[] to set
*/
explicit PreparedRouteFilter(const ConfigBlock &block);
/* virtual methods from class PreparedFilter */
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
PreparedRouteFilter::PreparedRouteFilter(const ConfigBlock &block)
{
/* TODO:
* With a more clever way of marking "don't copy to output N",
* This could easily be merged into a single loop with some
* dynamic realloc() instead of one count run and one malloc().
*/
sources.fill(-1);
min_input_channels = 0;
min_output_channels = 0;
// A cowardly default, just passthrough stereo
const char *routes = block.GetBlockValue("routes", "0>0, 1>1");
while (true) {
routes = StripLeft(routes);
char *endptr;
const unsigned source = strtoul(routes, &endptr, 10);
endptr = StripLeft(endptr);
if (endptr == routes || *endptr != '>')
throw std::runtime_error("Malformed 'routes' specification");
if (source >= MAX_CHANNELS)
throw FmtRuntimeError("Invalid source channel number: {}",
source);
if (source >= min_input_channels)
min_input_channels = source + 1;
routes = StripLeft(endptr + 1);
unsigned dest = strtoul(routes, &endptr, 10);
endptr = StripLeft(endptr);
if (endptr == routes)
throw std::runtime_error("Malformed 'routes' specification");
if (dest >= MAX_CHANNELS)
throw FmtRuntimeError("Invalid destination channel number: {}",
dest);
if (dest >= min_output_channels)
min_output_channels = dest + 1;
sources[dest] = source;
routes = endptr;
if (*routes == 0)
break;
if (*routes != ',')
throw std::runtime_error("Malformed 'routes' specification");
++routes;
}
}
static std::unique_ptr<PreparedFilter>
route_filter_init(const ConfigBlock &block)
{
return std::make_unique<PreparedRouteFilter>(block);
}
RouteFilter::RouteFilter(const AudioFormat &audio_format,
unsigned out_channels,
const std::array<int8_t, MAX_CHANNELS> &_sources)
:Filter(audio_format), sources(_sources), input_format(audio_format),
input_frame_size(input_format.GetFrameSize())
{
// Decide on an output format which has enough channels,
// and is otherwise identical
out_audio_format.channels = out_channels;
// Precalculate this simple value, to speed up allocation later
output_frame_size = out_audio_format.GetFrameSize();
}
std::unique_ptr<Filter>
PreparedRouteFilter::Open(AudioFormat &audio_format)
{
return std::make_unique<RouteFilter>(audio_format, min_output_channels,
sources);
}
std::span<const std::byte>
RouteFilter::FilterPCM(std::span<const std::byte> src)
{
size_t number_of_frames = src.size() / input_frame_size;
const size_t bytes_per_frame_per_channel = input_format.GetSampleSize();
// A moving pointer that always refers to channel 0 in the input, at the currently handled frame
const auto *base_source = (const uint8_t *)src.data();
// Grow our reusable buffer, if needed, and set the moving pointer
const size_t result_size = number_of_frames * output_frame_size;
void *const result = output_buffer.Get(result_size);
// A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
auto *chan_destination = (std::byte *)result;
// Perform our copy operations, with N input channels and M output channels
for (unsigned int s=0; s<number_of_frames; ++s) {
// Need to perform one copy per output channel
for (unsigned c = 0; c < out_audio_format.channels; ++c) {
if (sources[c] == -1 ||
(unsigned)sources[c] >= input_format.channels) {
// No source for this destination output,
// give it zeroes as input
PcmSilence({chan_destination, bytes_per_frame_per_channel},
input_format.format);
} else {
// Get the data from channel sources[c]
// and copy it to the output
const uint8_t *data = base_source +
(sources[c] * bytes_per_frame_per_channel);
memcpy(chan_destination,
data,
bytes_per_frame_per_channel);
}
// Move on to the next output channel
chan_destination += bytes_per_frame_per_channel;
}
// Go on to the next N input samples
base_source += input_frame_size;
}
// Here it is, ladies and gentlemen! Rerouted data!
return { (const std::byte *)result, result_size };
}
const FilterPlugin route_filter_plugin = {
"route",
route_filter_init,
};

@ -1,11 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#ifndef MPD_ROUTE_FILTER_PLUGIN_HXX
#define MPD_ROUTE_FILTER_PLUGIN_HXX
struct FilterPlugin;
extern const FilterPlugin route_filter_plugin;
#endif

@ -1,100 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "TwoFilters.hxx"
#include "pcm/AudioFormat.hxx"
#include "lib/fmt/AudioFormatFormatter.hxx"
#include "lib/fmt/RuntimeError.hxx"
#include "util/StringBuffer.hxx"
void
TwoFilters::Reset() noexcept
{
assert(first);
assert(second);
first->Reset();
second->Reset();
}
std::span<const std::byte>
TwoFilters::FilterPCM(std::span<const std::byte> src)
{
assert(first);
assert(second);
if (const auto dest = first->FilterPCM(src); dest.empty()) [[unlikely]]
/* no output from the first filter; pass the empty
buffer on, do not call the second filter */
return dest;
else
/* pass output from the first filter to the second
filter and return its result */
return second->FilterPCM(dest);
}
std::span<const std::byte>
TwoFilters::ReadMore()
{
assert(first);
assert(second);
/* first read all remaining data from the second filter */
if (auto result = second->ReadMore(); !result.empty())
return result;
/* now read more data from the first filter and process it
with the second filter */
if (auto result = first->ReadMore(); !result.empty())
/* output from the first Filter must be filtered by
the second Filter */
return second->FilterPCM(result);
/* both filters have been queried and there's no more data */
return {};
}
std::span<const std::byte>
TwoFilters::Flush()
{
assert(second);
/* first read all remaining data from the second filter */
if (auto result = second->ReadMore(); !result.empty())
return result;
/* now flush the first filter and process it with the second
filter */
if (first) {
if (auto result = first->Flush(); !result.empty())
/* output from the first Filter must be
filtered by the second Filter */
return second->FilterPCM(result);
/* the first filter is flushed completely and we don't
need it anymore; any further method calls that
would use it are illegal according to the Filter
API docs */
first.reset();
}
/* finally flush the second filter */
return second->Flush();
}
std::unique_ptr<Filter>
PreparedTwoFilters::Open(AudioFormat &audio_format)
{
auto a = first->Open(audio_format);
const auto &a_out_format = a->GetOutAudioFormat();
auto b_in_format = a_out_format;
auto b = second->Open(b_in_format);
if (b_in_format != a_out_format)
throw FmtRuntimeError("Audio format not supported by filter {:?}: {}",
second_name, a_out_format);
return std::make_unique<TwoFilters>(std::move(a),
std::move(b));
}

@ -1,66 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#pragma once
#ifndef MPD_TWO_FILTERS_HXX
#define MPD_TWO_FILTERS_HXX
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include <memory>
#include <string>
/**
* A #Filter implementation which chains two other filters.
*/
class TwoFilters final : public Filter {
std::unique_ptr<Filter> first, second;
public:
template<typename F, typename S>
TwoFilters(F &&_first, S &&_second) noexcept
:Filter(_second->GetOutAudioFormat()),
first(std::forward<F>(_first)),
second(std::forward<S>(_second)) {}
// virtual methods from class Filter
void Reset() noexcept override;
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
std::span<const std::byte> ReadMore() override;
std::span<const std::byte> Flush() override;
};
/**
* Like #TwoFilters, but implements the #PreparedFilter interface.
* Stub implementation - filter support not needed for database creation
*/
class PreparedTwoFilters final : public PreparedFilter {
std::unique_ptr<PreparedFilter> first, second;
std::string second_name;
public:
template<typename F, typename S, typename N>
PreparedTwoFilters(F &&_first, S &&_second, N &&_second_name) noexcept
:first(std::forward<F>(_first)),
second(std::forward<S>(_second)),
second_name(std::forward<N>(_second_name)) {}
std::unique_ptr<Filter> Open(AudioFormat &audio_format) override;
};
/**
* Create a #PreparedTwoFilters instance, but only if both parameters
* are not nullptr.
*/
template<typename F, typename S, typename N>
static std::unique_ptr<PreparedFilter>
ChainFilters(F &&first, S &&second, N &&second_name) noexcept
inline std::unique_ptr<PreparedFilter>
PreparedTwoFilters([[maybe_unused]] std::unique_ptr<PreparedFilter> a,
[[maybe_unused]] std::unique_ptr<PreparedFilter> b)
{
if (!second)
return std::forward<F>(first);
if (!first)
return std::forward<S>(second);
return nullptr;
}
return std::make_unique<PreparedTwoFilters>(std::forward<F>(first),
std::forward<S>(second),
std::forward<N>(second_name));
inline std::unique_ptr<PreparedFilter>
ChainFilters([[maybe_unused]] std::unique_ptr<PreparedFilter> a,
[[maybe_unused]] std::unique_ptr<PreparedFilter> b,
[[maybe_unused]] const char *name = nullptr)
{
return nullptr;
}
#endif

@ -1,71 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "VolumeFilterPlugin.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
#include "pcm/Volume.hxx"
#include "pcm/AudioFormat.hxx"
class VolumeFilter final : public Filter {
PcmVolume pv;
public:
explicit VolumeFilter(const AudioFormat &audio_format)
:Filter(audio_format) {
out_audio_format.format = pv.Open(out_audio_format.format,
true);
}
[[nodiscard]] unsigned GetVolume() const noexcept {
return pv.GetVolume();
}
void SetVolume(unsigned _volume) noexcept {
pv.SetVolume(_volume);
}
/* virtual methods from class Filter */
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
};
class PreparedVolumeFilter final : public PreparedFilter {
public:
/* virtual methods from class Filter */
std::unique_ptr<Filter> Open(AudioFormat &af) override;
};
std::unique_ptr<Filter>
PreparedVolumeFilter::Open(AudioFormat &audio_format)
{
return std::make_unique<VolumeFilter>(audio_format);
}
std::span<const std::byte>
VolumeFilter::FilterPCM(std::span<const std::byte> src)
{
return pv.Apply(src);
}
std::unique_ptr<PreparedFilter>
volume_filter_prepare() noexcept
{
return std::make_unique<PreparedVolumeFilter>();
}
unsigned
volume_filter_get(const Filter *_filter) noexcept
{
const auto *filter =
(const VolumeFilter *)_filter;
return filter->GetVolume();
}
void
volume_filter_set(Filter *_filter, unsigned volume) noexcept
{
auto *filter = (VolumeFilter *)_filter;
filter->SetVolume(volume);
}

@ -1,21 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
// STUB FILE - Filter support removed for mpd-dbcreate
#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX
#define MPD_VOLUME_FILTER_PLUGIN_HXX
#include "filter/Prepared.hxx"
#include <memory>
class PreparedFilter;
class Filter;
std::unique_ptr<PreparedFilter>
volume_filter_prepare() noexcept;
/**
* Stub implementation - filter support not needed for database creation
*/
inline void
volume_filter_set(Filter *, unsigned) noexcept
{
// No-op stub
}
unsigned
volume_filter_get(const Filter *filter) noexcept;
void
volume_filter_set(Filter *filter, unsigned volume) noexcept;
inline std::unique_ptr<PreparedFilter>
volume_filter_prepare() noexcept
{
return nullptr;
}
#endif

@ -1,38 +0,0 @@
filter_plugins_sources = []
filter_plugins_deps = [fmt_dep]
if libavfilter_dep.found()
filter_plugins_sources += [
'FfmpegFilter.cxx',
'FfmpegFilterPlugin.cxx',
'HdcdFilterPlugin.cxx',
]
filter_plugins_deps += ffmpeg_dep
endif
filter_plugins = static_library(
'filter_plugins',
'NullFilterPlugin.cxx',
'TwoFilters.cxx',
'AutoConvertFilterPlugin.cxx',
'ConvertFilterPlugin.cxx',
'RouteFilterPlugin.cxx',
'NormalizeFilterPlugin.cxx',
'ReplayGainFilterPlugin.cxx',
'VolumeFilterPlugin.cxx',
filter_plugins_sources,
include_directories: inc,
dependencies: [
filter_plugins_deps,
log_dep,
],
)
filter_plugins_dep = declare_dependency(
link_with: filter_plugins,
dependencies: [
filter_api_dep,
pcm_dep,
config_dep,
] + filter_plugins_deps,
)

@ -11,7 +11,7 @@ output_api = static_library(
output_api_dep = declare_dependency(
link_with: output_api,
dependencies: [
filter_plugins_dep,
# filter_plugins_dep, # Removed - not needed for database creation
mixer_plugins_dep,
],
)
@ -56,7 +56,7 @@ output_glue = static_library(
output_glue_dep = declare_dependency(
link_with: output_glue,
dependencies: [
filter_glue_dep,
# filter_glue_dep, # Removed - not needed for database creation
mixer_plugins_dep,
],
)

@ -482,16 +482,17 @@ test(
protocol: 'gtest',
)
executable(
'run_filter',
'run_filter.cxx',
'ReadFrames.cxx',
include_directories: inc,
dependencies: [
filter_glue_dep,
cmdline_dep,
],
)
# Removed - filter plugins not needed for database creation
# executable(
# 'run_filter',
# 'run_filter.cxx',
# 'ReadFrames.cxx',
# include_directories: inc,
# dependencies: [
# filter_glue_dep,
# cmdline_dep,
# ],
# )
executable(
'software_volume',
@ -546,28 +547,28 @@ executable(
)
#
# Encoder
# Encoder - Removed, not needed for database creation
#
if need_encoder
executable(
'run_encoder',
'run_encoder.cxx',
include_directories: inc,
dependencies: [
encoder_glue_dep,
],
)
executable(
'test_vorbis_encoder',
'test_vorbis_encoder.cxx',
include_directories: inc,
dependencies: [
encoder_glue_dep,
],
)
endif
# if need_encoder
# executable(
# 'run_encoder',
# 'run_encoder.cxx',
# include_directories: inc,
# dependencies: [
# encoder_glue_dep,
# ],
# )
#
# executable(
# 'test_vorbis_encoder',
# 'test_vorbis_encoder.cxx',
# include_directories: inc,
# dependencies: [
# encoder_glue_dep,
# ],
# )
# endif
#
# Output
@ -579,7 +580,7 @@ executable(
include_directories: inc,
dependencies: [
output_registry_dep,
encoder_glue_dep,
# encoder_glue_dep, # Removed - not needed for database creation
event_dep,
cmdline_dep,
],

Loading…
Cancel
Save