Another 1.0 candidate before I start stripping meson options
parent
6075950a35
commit
077d715da0
@ -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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_ENCODER_CONFIGURED_HXX
|
#ifndef MPD_ENCODER_CONFIGURED_HXX
|
||||||
#define MPD_ENCODER_CONFIGURED_HXX
|
#define MPD_ENCODER_CONFIGURED_HXX
|
||||||
|
|
||||||
|
#include "EncoderInterface.hxx"
|
||||||
|
|
||||||
struct ConfigBlock;
|
struct ConfigBlock;
|
||||||
class PreparedEncoder;
|
class AudioFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a #PreparedEncoder instance from the settings in the
|
* Stub implementation - encoder support not needed for database creation
|
||||||
* #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"
|
|
||||||
*/
|
*/
|
||||||
PreparedEncoder *
|
inline EncoderPtr
|
||||||
CreateConfiguredEncoder(const ConfigBlock &block, bool shout_legacy=false);
|
encoder_init([[maybe_unused]] const ConfigBlock &block,
|
||||||
|
[[maybe_unused]] AudioFormat &audio_format)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_ENCODER_INTERFACE_HXX
|
#ifndef MPD_ENCODER_INTERFACE_HXX
|
||||||
#define MPD_ENCODER_INTERFACE_HXX
|
#define MPD_ENCODER_INTERFACE_HXX
|
||||||
|
|
||||||
#include <cstddef>
|
#include <memory>
|
||||||
#include <span>
|
|
||||||
|
|
||||||
struct AudioFormat;
|
|
||||||
struct Tag;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub implementation - encoder support not needed for database creation
|
||||||
|
*/
|
||||||
class Encoder {
|
class Encoder {
|
||||||
const bool implements_tag;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Encoder(bool _implements_tag) noexcept
|
virtual ~Encoder() = default;
|
||||||
: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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PreparedEncoder {
|
using EncoderPtr = std::unique_ptr<Encoder>;
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// 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 <iterator>
|
||||||
#include "util/TerminatedArray.hxx"
|
|
||||||
|
|
||||||
struct EncoderPlugin;
|
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
|
bool operator==(const EncoderPluginIterator &) const noexcept { return true; }
|
||||||
GetAllEncoderPlugins() noexcept
|
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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
inline EncoderPluginIterator
|
||||||
* Looks up an encoder plugin by its name.
|
encoder_plugins_end() noexcept
|
||||||
*
|
{
|
||||||
* @param name the encoder name to look for
|
return {};
|
||||||
* @return the encoder plugin with the specified name, or nullptr if none
|
}
|
||||||
* was found
|
|
||||||
*/
|
#endif
|
||||||
const EncoderPlugin *
|
|
||||||
encoder_plugin_get(const char *name);
|
|
||||||
|
|||||||
@ -1,32 +1,17 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_ENCODER_PLUGIN_HXX
|
#ifndef MPD_ENCODER_PLUGIN_HXX
|
||||||
#define MPD_ENCODER_PLUGIN_HXX
|
#define MPD_ENCODER_PLUGIN_HXX
|
||||||
|
|
||||||
class PreparedEncoder;
|
|
||||||
struct ConfigBlock;
|
struct ConfigBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub implementation - encoder support not needed for database creation
|
||||||
|
*/
|
||||||
struct EncoderPlugin {
|
struct EncoderPlugin {
|
||||||
const char *name;
|
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
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||||
|
|
||||||
#include "WaveEncoderPlugin.hxx"
|
#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 = {
|
const EncoderPlugin wave_encoder_plugin = {
|
||||||
"wave",
|
"wave",
|
||||||
wave_encoder_init,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_ENCODER_WAVE_HXX
|
#ifndef MPD_WAVE_ENCODER_PLUGIN_HXX
|
||||||
#define MPD_ENCODER_WAVE_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
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_FILTER_FACTORY_HXX
|
#ifndef MPD_FILTER_FACTORY_HXX
|
||||||
#define MPD_FILTER_FACTORY_HXX
|
#define MPD_FILTER_FACTORY_HXX
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct ConfigData;
|
struct ConfigData;
|
||||||
class PreparedFilter;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub implementation - filter support not needed for database creation
|
||||||
|
*/
|
||||||
class FilterFactory {
|
class FilterFactory {
|
||||||
const ConfigData &config;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FilterFactory(const ConfigData &_config) noexcept
|
FilterFactory([[maybe_unused]] const ConfigData &config) {}
|
||||||
:config(_config) {}
|
|
||||||
|
|
||||||
std::unique_ptr<PreparedFilter> MakeFilter(const char *name);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_FILTER_HXX
|
#ifndef MPD_FILTER_HXX
|
||||||
#define MPD_FILTER_HXX
|
#define MPD_FILTER_HXX
|
||||||
|
|
||||||
#include "pcm/AudioFormat.hxx"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class Filter {
|
struct AudioFormat;
|
||||||
protected:
|
|
||||||
AudioFormat out_audio_format;
|
|
||||||
|
|
||||||
explicit Filter(AudioFormat _out_audio_format) noexcept
|
|
||||||
:out_audio_format(_out_audio_format) {
|
|
||||||
assert(out_audio_format.IsValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub implementation - filter support not needed for database creation
|
||||||
|
*/
|
||||||
|
class Filter {
|
||||||
public:
|
public:
|
||||||
virtual ~Filter() noexcept = default;
|
virtual ~Filter() = default;
|
||||||
|
|
||||||
/**
|
virtual std::span<const std::byte> FilterPCM(std::span<const std::byte> src) {
|
||||||
* Returns the #AudioFormat produced by FilterPCM().
|
return src; // Pass-through stub
|
||||||
*/
|
|
||||||
const AudioFormat &GetOutAudioFormat() const noexcept {
|
|
||||||
return out_audio_format;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
virtual std::span<const std::byte> Flush() {
|
||||||
* Reset the filter's state, e.g. drop/flush buffers.
|
return {}; // Empty stub
|
||||||
*/
|
|
||||||
virtual void Reset() noexcept {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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() {
|
virtual std::span<const std::byte> ReadMore() {
|
||||||
return {};
|
return {}; // Empty stub
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
virtual void Reset() noexcept {
|
||||||
* Flush pending data and return it. This should be called
|
// No-op stub
|
||||||
* 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 const AudioFormat &GetOutAudioFormat() const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PreparedFilter; // Forward declaration - see Prepared.hxx for full definition
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_FILTER_LOAD_CHAIN_HXX
|
#ifndef MPD_FILTER_LOAD_CHAIN_HXX
|
||||||
#define MPD_FILTER_LOAD_CHAIN_HXX
|
#define MPD_FILTER_LOAD_CHAIN_HXX
|
||||||
|
|
||||||
|
#include "Prepared.hxx"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class FilterFactory;
|
class FilterFactory;
|
||||||
class PreparedFilter;
|
struct ConfigBlock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a filter chain from a configuration string on the form
|
* Stub implementation - filter support not needed for database creation
|
||||||
* "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
|
|
||||||
*/
|
*/
|
||||||
void
|
inline std::unique_ptr<PreparedFilter>
|
||||||
filter_chain_parse(std::unique_ptr<PreparedFilter> &chain,
|
filter_chain_parse([[maybe_unused]] std::unique_ptr<PreparedFilter> &prepared,
|
||||||
FilterFactory &factory,
|
[[maybe_unused]] FilterFactory &factory,
|
||||||
const char *spec);
|
[[maybe_unused]] const char *spec)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::unique_ptr<PreparedFilter>
|
||||||
|
filter_chain_new([[maybe_unused]] const ConfigBlock *block)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_FILTER_OBSERVER_HXX
|
#ifndef MPD_FILTER_OBSERVER_HXX
|
||||||
#define MPD_FILTER_OBSERVER_HXX
|
#define MPD_FILTER_OBSERVER_HXX
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class PreparedFilter;
|
|
||||||
class Filter;
|
class Filter;
|
||||||
|
class PreparedFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class which observes calls to a #PreparedFilter and allows
|
* Stub implementation - filter support not needed for database creation
|
||||||
* the caller to access the #Filter instances created by it.
|
|
||||||
*/
|
*/
|
||||||
class FilterObserver {
|
class FilterObserver {
|
||||||
class PreparedProxy;
|
public:
|
||||||
class Proxy;
|
FilterObserver() noexcept = default;
|
||||||
|
|
||||||
PreparedProxy *proxy = nullptr;
|
Filter *Get() noexcept { return nullptr; }
|
||||||
|
|
||||||
public:
|
void Set(Filter *) noexcept {}
|
||||||
/**
|
|
||||||
* @return a proxy object
|
|
||||||
*/
|
|
||||||
std::unique_ptr<PreparedFilter> Set(std::unique_ptr<PreparedFilter> pf);
|
|
||||||
|
|
||||||
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
|
#endif
|
||||||
|
|||||||
@ -1,28 +1,25 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_PREPARED_FILTER_HXX
|
#ifndef MPD_FILTER_PREPARED_HXX
|
||||||
#define MPD_PREPARED_FILTER_HXX
|
#define MPD_FILTER_PREPARED_HXX
|
||||||
|
|
||||||
|
#include "Filter.hxx"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
struct AudioFormat;
|
struct AudioFormat;
|
||||||
class Filter;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub implementation - filter support not needed for database creation
|
||||||
|
*/
|
||||||
class PreparedFilter {
|
class PreparedFilter {
|
||||||
public:
|
public:
|
||||||
virtual ~PreparedFilter() = default;
|
virtual ~PreparedFilter() = default;
|
||||||
|
|
||||||
/**
|
virtual std::unique_ptr<Filter> Open([[maybe_unused]] const AudioFormat &af) {
|
||||||
* Opens the filter, preparing it for FilterPCM().
|
return nullptr; // Stub returns nullptr
|
||||||
*
|
}
|
||||||
* 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#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,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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
#ifndef MPD_AUTO_CONVERT_FILTER_PLUGIN_HXX
|
||||||
#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
#define MPD_AUTO_CONVERT_FILTER_PLUGIN_HXX
|
||||||
|
|
||||||
|
#include "filter/Prepared.hxx"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class PreparedFilter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new "autoconvert" filter. When opened, it ensures that
|
* Stub implementation - filter support not needed for database creation
|
||||||
* the input audio format isn't changed. If the underlying filter
|
|
||||||
* requests a different format, it automatically creates a
|
|
||||||
* convert_filter.
|
|
||||||
*/
|
*/
|
||||||
std::unique_ptr<PreparedFilter>
|
inline std::unique_ptr<PreparedFilter>
|
||||||
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept;
|
autoconvert_filter_new([[maybe_unused]] std::unique_ptr<PreparedFilter> a = nullptr)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX
|
#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX
|
||||||
#define MPD_CONVERT_FILTER_PLUGIN_HXX
|
#define MPD_CONVERT_FILTER_PLUGIN_HXX
|
||||||
|
|
||||||
|
#include "filter/Prepared.hxx"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class PreparedFilter;
|
|
||||||
class Filter;
|
|
||||||
struct AudioFormat;
|
struct AudioFormat;
|
||||||
|
class Filter;
|
||||||
std::unique_ptr<PreparedFilter>
|
|
||||||
convert_filter_prepare() noexcept;
|
|
||||||
|
|
||||||
std::unique_ptr<Filter>
|
|
||||||
convert_filter_new(AudioFormat in_audio_format,
|
|
||||||
AudioFormat out_audio_format);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the output audio format for the specified filter. You must
|
* Stub implementation - filter support not needed for database creation
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
void
|
inline std::unique_ptr<PreparedFilter>
|
||||||
convert_filter_set(Filter *filter, AudioFormat out_audio_format);
|
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
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_NORMALIZE_FILTER_PLUGIN_HXX
|
#ifndef MPD_NORMALIZE_FILTER_PLUGIN_HXX
|
||||||
#define MPD_NORMALIZE_FILTER_PLUGIN_HXX
|
#define MPD_NORMALIZE_FILTER_PLUGIN_HXX
|
||||||
|
|
||||||
|
#include "filter/Prepared.hxx"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
struct FilterPlugin;
|
struct ConfigBlock;
|
||||||
class PreparedFilter;
|
|
||||||
|
|
||||||
extern const FilterPlugin normalize_filter_plugin;
|
/**
|
||||||
|
* Stub implementation - filter support not needed for database creation
|
||||||
std::unique_ptr<PreparedFilter>
|
*/
|
||||||
normalize_filter_prepare() noexcept;
|
inline std::unique_ptr<PreparedFilter>
|
||||||
|
normalize_filter_prepare([[maybe_unused]] const ConfigBlock *block = nullptr)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
|
#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
|
||||||
#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
|
#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
|
||||||
|
|
||||||
#include "ReplayGainMode.hxx"
|
#include "filter/Prepared.hxx"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
class Filter;
|
|
||||||
class PreparedFilter;
|
|
||||||
class Mixer;
|
|
||||||
struct ReplayGainConfig;
|
struct ReplayGainConfig;
|
||||||
struct ReplayGainInfo;
|
enum class ReplayGainMode : uint8_t;
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables or disables the hardware mixer for applying replay gain.
|
* Stub implementation - filter support not needed for database creation
|
||||||
*
|
|
||||||
* @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).
|
|
||||||
*/
|
*/
|
||||||
void
|
inline std::unique_ptr<PreparedFilter>
|
||||||
replay_gain_filter_set_mixer(PreparedFilter &_filter, Mixer *mixer,
|
NewReplayGainFilter([[maybe_unused]] const ReplayGainConfig &config,
|
||||||
unsigned base);
|
[[maybe_unused]] bool allow_convert = false)
|
||||||
|
{
|
||||||
/**
|
return nullptr;
|
||||||
* Sets a new #ReplayGainInfo at the beginning of a new song.
|
}
|
||||||
*
|
|
||||||
* @param info the new #ReplayGainInfo value, or nullptr if no replay
|
inline void
|
||||||
* gain data is available for the current song
|
replay_gain_filter_set_mode([[maybe_unused]] Filter &,
|
||||||
*/
|
[[maybe_unused]] ReplayGainMode) noexcept
|
||||||
void
|
{
|
||||||
replay_gain_filter_set_info(Filter &filter, const ReplayGainInfo *info);
|
// No-op stub
|
||||||
|
}
|
||||||
void
|
|
||||||
replay_gain_filter_set_mode(Filter &filter, ReplayGainMode mode);
|
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
|
#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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// 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 "filter/Prepared.hxx"
|
||||||
|
|
||||||
#include <memory>
|
#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 {
|
inline std::unique_ptr<PreparedFilter>
|
||||||
std::unique_ptr<PreparedFilter> first, second;
|
PreparedTwoFilters([[maybe_unused]] std::unique_ptr<PreparedFilter> a,
|
||||||
std::string second_name;
|
[[maybe_unused]] std::unique_ptr<PreparedFilter> b)
|
||||||
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
if (!second)
|
return nullptr;
|
||||||
return std::forward<F>(first);
|
}
|
||||||
|
|
||||||
if (!first)
|
|
||||||
return std::forward<S>(second);
|
|
||||||
|
|
||||||
return std::make_unique<PreparedTwoFilters>(std::forward<F>(first),
|
inline std::unique_ptr<PreparedFilter>
|
||||||
std::forward<S>(second),
|
ChainFilters([[maybe_unused]] std::unique_ptr<PreparedFilter> a,
|
||||||
std::forward<N>(second_name));
|
[[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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Copyright The Music Player Daemon Project
|
// Copyright The Music Player Daemon Project
|
||||||
|
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||||
|
|
||||||
#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX
|
#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX
|
||||||
#define MPD_VOLUME_FILTER_PLUGIN_HXX
|
#define MPD_VOLUME_FILTER_PLUGIN_HXX
|
||||||
|
|
||||||
|
#include "filter/Prepared.hxx"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class PreparedFilter;
|
|
||||||
class Filter;
|
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
|
inline std::unique_ptr<PreparedFilter>
|
||||||
volume_filter_get(const Filter *filter) noexcept;
|
volume_filter_prepare() noexcept
|
||||||
|
{
|
||||||
void
|
return nullptr;
|
||||||
volume_filter_set(Filter *filter, unsigned volume) noexcept;
|
}
|
||||||
|
|
||||||
#endif
|
#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,
|
|
||||||
)
|
|
||||||
Loading…
Reference in New Issue