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
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_ENCODER_CONFIGURED_HXX
|
||||
#define MPD_ENCODER_CONFIGURED_HXX
|
||||
|
||||
#include "EncoderInterface.hxx"
|
||||
|
||||
struct ConfigBlock;
|
||||
class PreparedEncoder;
|
||||
class AudioFormat;
|
||||
|
||||
/**
|
||||
* Create a #PreparedEncoder instance from the settings in the
|
||||
* #ConfigBlock. Its "encoder" setting is used to choose the encoder
|
||||
* plugin.
|
||||
*
|
||||
* Throws an exception on error.
|
||||
*
|
||||
* @param shout_legacy enable the "shout" plugin legacy configuration?
|
||||
* i.e. fall back to setting "encoding" instead of "encoder"
|
||||
* Stub implementation - encoder support not needed for database creation
|
||||
*/
|
||||
PreparedEncoder *
|
||||
CreateConfiguredEncoder(const ConfigBlock &block, bool shout_legacy=false);
|
||||
inline EncoderPtr
|
||||
encoder_init([[maybe_unused]] const ConfigBlock &block,
|
||||
[[maybe_unused]] AudioFormat &audio_format)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
/*
|
||||
* This header is included by encoder plugins.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MPD_ENCODER_API_HXX
|
||||
#define MPD_ENCODER_API_HXX
|
||||
|
||||
// IWYU pragma: begin_exports
|
||||
|
||||
#include "EncoderInterface.hxx"
|
||||
#include "EncoderPlugin.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "config/Block.hxx"
|
||||
|
||||
// IWYU pragma: end_exports
|
||||
|
||||
#endif
|
||||
@ -1,122 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_ENCODER_INTERFACE_HXX
|
||||
#define MPD_ENCODER_INTERFACE_HXX
|
||||
|
||||
#include <cstddef>
|
||||
#include <span>
|
||||
|
||||
struct AudioFormat;
|
||||
struct Tag;
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* Stub implementation - encoder support not needed for database creation
|
||||
*/
|
||||
class Encoder {
|
||||
const bool implements_tag;
|
||||
|
||||
public:
|
||||
explicit Encoder(bool _implements_tag) noexcept
|
||||
:implements_tag(_implements_tag) {}
|
||||
virtual ~Encoder() noexcept = default;
|
||||
|
||||
bool ImplementsTag() const noexcept {
|
||||
return implements_tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the stream: flushes the encoder object, generate an
|
||||
* end-of-stream marker (if applicable), make everything which
|
||||
* might currently be buffered available by Read().
|
||||
*
|
||||
* After this function has been called, the encoder may not be
|
||||
* usable for more data, and only Read() can be called.
|
||||
*
|
||||
* Throws on error.
|
||||
*/
|
||||
virtual void End() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes an encoder object, make everything which might
|
||||
* currently be buffered available by Read().
|
||||
*
|
||||
* Throws on error.
|
||||
*/
|
||||
virtual void Flush() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare for sending a tag to the encoder. This is used by
|
||||
* some encoders to flush the previous sub-stream, in
|
||||
* preparation to begin a new one.
|
||||
*
|
||||
* Throws on error.
|
||||
*/
|
||||
virtual void PreTag() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a tag to the encoder.
|
||||
*
|
||||
* Instructions: call PreTag(); then obtain flushed data with
|
||||
* Read(); finally call Tag() and again Read().
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param tag the tag object
|
||||
*/
|
||||
virtual void SendTag([[maybe_unused]] const Tag &tag) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes raw PCM data to the encoder.
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param data the buffer containing PCM samples
|
||||
* @param length the length of the buffer in bytes
|
||||
*/
|
||||
virtual void Write(std::span<const std::byte> src) = 0;
|
||||
|
||||
/**
|
||||
* Reads encoded data from the encoder.
|
||||
*
|
||||
* Call this repeatedly after End(), Flush(), PreTag(), SendTag() and
|
||||
* Write() until no more data is returned.
|
||||
*
|
||||
* @param buffer a buffer that can be used to write data into
|
||||
*
|
||||
* @return the portion of the buffer that was filled (but may
|
||||
* also point to a different buffer, e.g. one owned by this object)
|
||||
*/
|
||||
virtual std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept = 0;
|
||||
virtual ~Encoder() = default;
|
||||
};
|
||||
|
||||
class PreparedEncoder {
|
||||
public:
|
||||
virtual ~PreparedEncoder() noexcept = default;
|
||||
|
||||
/**
|
||||
* Create an #Encoder instance.
|
||||
*
|
||||
* After this function returns successfully and before the
|
||||
* first Encoder::Write() call, you should invoke
|
||||
* Encoder::Read() to obtain the file header.
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param audio_format the encoder's input audio format; the plugin
|
||||
* may modify the struct to adapt it to its abilities
|
||||
*/
|
||||
virtual Encoder *Open(AudioFormat &audio_format) = 0;
|
||||
|
||||
/**
|
||||
* Get mime type of encoded content.
|
||||
*
|
||||
* @return an constant string, nullptr on failure
|
||||
*/
|
||||
virtual const char *GetMimeType() const noexcept {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
using EncoderPtr = std::unique_ptr<Encoder>;
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "config.h"
|
||||
#include "EncoderList.hxx"
|
||||
#include "EncoderPlugin.hxx"
|
||||
#include "encoder/Features.h"
|
||||
#include "plugins/NullEncoderPlugin.hxx"
|
||||
#include "plugins/WaveEncoderPlugin.hxx"
|
||||
#include "plugins/VorbisEncoderPlugin.hxx"
|
||||
#include "plugins/OpusEncoderPlugin.hxx"
|
||||
#include "plugins/FlacEncoderPlugin.hxx"
|
||||
#include "plugins/ShineEncoderPlugin.hxx"
|
||||
#include "plugins/LameEncoderPlugin.hxx"
|
||||
#include "plugins/TwolameEncoderPlugin.hxx"
|
||||
#include "decoder/Features.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
constinit const EncoderPlugin *const encoder_plugins[] = {
|
||||
&null_encoder_plugin,
|
||||
#ifdef ENABLE_VORBISENC
|
||||
&vorbis_encoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_OPUS
|
||||
&opus_encoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_LAME
|
||||
&lame_encoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_TWOLAME
|
||||
&twolame_encoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_WAVE_ENCODER
|
||||
&wave_encoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_FLAC_ENCODER
|
||||
&flac_encoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_SHINE
|
||||
&shine_encoder_plugin,
|
||||
#endif
|
||||
nullptr
|
||||
};
|
||||
|
||||
const EncoderPlugin *
|
||||
encoder_plugin_get(const char *name)
|
||||
{
|
||||
for (const auto &plugin : GetAllEncoderPlugins()) {
|
||||
if (strcmp(plugin.name, name) == 0)
|
||||
return &plugin;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@ -1,27 +1,41 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||
|
||||
#pragma once
|
||||
#ifndef MPD_ENCODER_LIST_HXX
|
||||
#define MPD_ENCODER_LIST_HXX
|
||||
|
||||
#include "util/DereferenceIterator.hxx"
|
||||
#include "util/TerminatedArray.hxx"
|
||||
#include <iterator>
|
||||
|
||||
struct EncoderPlugin;
|
||||
|
||||
extern const EncoderPlugin *const encoder_plugins[];
|
||||
/**
|
||||
* Stub implementation - encoder support not needed for database creation
|
||||
*/
|
||||
class EncoderPluginIterator {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = const EncoderPlugin *;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
|
||||
static inline auto
|
||||
GetAllEncoderPlugins() noexcept
|
||||
bool operator==(const EncoderPluginIterator &) const noexcept { return true; }
|
||||
bool operator!=(const EncoderPluginIterator &) const noexcept { return false; }
|
||||
EncoderPluginIterator &operator++() noexcept { return *this; }
|
||||
const EncoderPlugin *operator*() const noexcept { return nullptr; }
|
||||
};
|
||||
|
||||
inline EncoderPluginIterator
|
||||
encoder_plugins_begin() noexcept
|
||||
{
|
||||
return DereferenceContainerAdapter{TerminatedArray<const EncoderPlugin *const, nullptr>{encoder_plugins}};
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up an encoder plugin by its name.
|
||||
*
|
||||
* @param name the encoder name to look for
|
||||
* @return the encoder plugin with the specified name, or nullptr if none
|
||||
* was found
|
||||
*/
|
||||
const EncoderPlugin *
|
||||
encoder_plugin_get(const char *name);
|
||||
inline EncoderPluginIterator
|
||||
encoder_plugins_end() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,32 +1,17 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_ENCODER_PLUGIN_HXX
|
||||
#define MPD_ENCODER_PLUGIN_HXX
|
||||
|
||||
class PreparedEncoder;
|
||||
struct ConfigBlock;
|
||||
|
||||
/**
|
||||
* Stub implementation - encoder support not needed for database creation
|
||||
*/
|
||||
struct EncoderPlugin {
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* Throws #std::runtime_error on error.
|
||||
*/
|
||||
PreparedEncoder *(*init)(const ConfigBlock &block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new encoder object.
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
*
|
||||
* @param plugin the encoder plugin
|
||||
*/
|
||||
static inline PreparedEncoder *
|
||||
encoder_init(const EncoderPlugin &plugin, const ConfigBlock &block)
|
||||
{
|
||||
return plugin.init(block);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_ENCODER_FEATURES_H
|
||||
#define MPD_ENCODER_FEATURES_H
|
||||
|
||||
// Encoder support disabled for database creation tool
|
||||
#define ENABLE_ENCODER 0
|
||||
|
||||
#endif
|
||||
@ -1,23 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "ToOutputStream.hxx"
|
||||
#include "EncoderInterface.hxx"
|
||||
#include "io/OutputStream.hxx"
|
||||
|
||||
void
|
||||
EncoderToOutputStream(OutputStream &os, Encoder &encoder)
|
||||
{
|
||||
while (true) {
|
||||
/* read from the encoder */
|
||||
|
||||
std::byte buffer[32768];
|
||||
const auto r = encoder.Read(buffer);
|
||||
if (r.empty())
|
||||
return;
|
||||
|
||||
/* write everything to the stream */
|
||||
|
||||
os.Write(r);
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#pragma once
|
||||
|
||||
class OutputStream;
|
||||
class Encoder;
|
||||
|
||||
/**
|
||||
* Read all available output from the #Encoder and write it to the
|
||||
* #OutputStream.
|
||||
*
|
||||
* Throws on error.
|
||||
*/
|
||||
void
|
||||
EncoderToOutputStream(OutputStream &os, Encoder &encoder);
|
||||
@ -1,50 +0,0 @@
|
||||
encoder_features = configuration_data()
|
||||
|
||||
encoder_features.set('ENABLE_ENCODER', need_encoder)
|
||||
|
||||
if not need_encoder
|
||||
if need_wave_encoder
|
||||
# Special case for the Snapcast output plugin which only needs the
|
||||
# PCM wave encoder encoder plugin
|
||||
encoder_glue = static_library(
|
||||
'encoder_glue',
|
||||
'plugins/WaveEncoderPlugin.cxx',
|
||||
include_directories: inc,
|
||||
)
|
||||
|
||||
encoder_glue_dep = declare_dependency(
|
||||
link_with: encoder_glue,
|
||||
)
|
||||
|
||||
configure_file(output: 'Features.h', configuration: encoder_features)
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
encoder_glue_dep = dependency('', required: false)
|
||||
configure_file(output: 'Features.h', configuration: encoder_features)
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
encoder_api_dep = declare_dependency()
|
||||
|
||||
subdir('plugins')
|
||||
|
||||
encoder_glue = static_library(
|
||||
'encoder_glue',
|
||||
'Configured.cxx',
|
||||
'ToOutputStream.cxx',
|
||||
'EncoderList.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
fmt_dep,
|
||||
],
|
||||
)
|
||||
|
||||
encoder_glue_dep = declare_dependency(
|
||||
link_with: encoder_glue,
|
||||
dependencies: [
|
||||
encoder_plugins_dep,
|
||||
],
|
||||
)
|
||||
|
||||
configure_file(output: 'Features.h', configuration: encoder_features)
|
||||
@ -1,288 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "FlacEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "tag/Names.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Buffer.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
#include "util/Serial.hxx"
|
||||
#include "util/SpanCast.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
|
||||
#include <FLAC/stream_encoder.h>
|
||||
#include <FLAC/metadata.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility> // for std::unreachable()
|
||||
|
||||
class FlacEncoder final : public Encoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
FLAC__StreamEncoder *const fse;
|
||||
const unsigned compression;
|
||||
const bool oggflac;
|
||||
|
||||
PcmBuffer expand_buffer;
|
||||
|
||||
/**
|
||||
* This buffer will hold encoded data from libFLAC until it is
|
||||
* picked up with Read().
|
||||
*/
|
||||
DynamicFifoBuffer<std::byte> output_buffer{8192};
|
||||
|
||||
public:
|
||||
FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining);
|
||||
|
||||
~FlacEncoder() noexcept override {
|
||||
FLAC__stream_encoder_delete(fse);
|
||||
}
|
||||
|
||||
FlacEncoder(const FlacEncoder &) = delete;
|
||||
FlacEncoder &operator=(const FlacEncoder &) = delete;
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void End() override {
|
||||
(void) FLAC__stream_encoder_finish(fse);
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
}
|
||||
|
||||
void PreTag() override {
|
||||
(void) FLAC__stream_encoder_finish(fse);
|
||||
}
|
||||
|
||||
void SendTag(const Tag &tag) override;
|
||||
|
||||
void Write(std::span<const std::byte> src) override;
|
||||
|
||||
std::span<const std::byte> Read(std::span<std::byte>) noexcept override {
|
||||
auto r = output_buffer.Read();
|
||||
output_buffer.Consume(r.size());
|
||||
return r;
|
||||
}
|
||||
|
||||
private:
|
||||
static FLAC__StreamEncoderWriteStatus WriteCallback(const FLAC__StreamEncoder *,
|
||||
const FLAC__byte data[],
|
||||
size_t bytes,
|
||||
[[maybe_unused]] unsigned samples,
|
||||
[[maybe_unused]] unsigned current_frame,
|
||||
void *client_data) noexcept {
|
||||
auto &encoder = *(FlacEncoder *)client_data;
|
||||
encoder.output_buffer.Append({(const std::byte *)data, bytes});
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class PreparedFlacEncoder final : public PreparedEncoder {
|
||||
const unsigned compression;
|
||||
const bool oggchaining;
|
||||
const bool oggflac;
|
||||
|
||||
public:
|
||||
explicit PreparedFlacEncoder(const ConfigBlock &block);
|
||||
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &audio_format) override;
|
||||
|
||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||
if(oggflac)
|
||||
return "audio/ogg";
|
||||
return "audio/flac";
|
||||
}
|
||||
};
|
||||
|
||||
PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block)
|
||||
:compression(block.GetBlockValue("compression", 5U)),
|
||||
oggchaining(block.GetBlockValue("oggchaining",false)),
|
||||
oggflac(block.GetBlockValue("oggflac",false) || oggchaining)
|
||||
{
|
||||
}
|
||||
|
||||
static PreparedEncoder *
|
||||
flac_encoder_init(const ConfigBlock &block)
|
||||
{
|
||||
return new PreparedFlacEncoder(block);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
|
||||
const AudioFormat &audio_format)
|
||||
{
|
||||
unsigned bits_per_sample;
|
||||
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S8:
|
||||
bits_per_sample = 8;
|
||||
break;
|
||||
|
||||
case SampleFormat::S16:
|
||||
bits_per_sample = 16;
|
||||
break;
|
||||
|
||||
default:
|
||||
bits_per_sample = 24;
|
||||
}
|
||||
|
||||
if (!FLAC__stream_encoder_set_compression_level(fse, compression))
|
||||
throw FmtRuntimeError("error setting flac compression to {}",
|
||||
compression);
|
||||
|
||||
if (!FLAC__stream_encoder_set_channels(fse, audio_format.channels))
|
||||
throw FmtRuntimeError("error setting flac channels num to {}",
|
||||
audio_format.channels);
|
||||
|
||||
if (!FLAC__stream_encoder_set_bits_per_sample(fse, bits_per_sample))
|
||||
throw FmtRuntimeError("error setting flac bit format to {}",
|
||||
bits_per_sample);
|
||||
|
||||
if (!FLAC__stream_encoder_set_sample_rate(fse,
|
||||
audio_format.sample_rate))
|
||||
throw FmtRuntimeError("error setting flac sample rate to {}",
|
||||
audio_format.sample_rate);
|
||||
|
||||
if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||
GenerateSerial()))
|
||||
throw std::runtime_error{"error setting ogg serial number"};
|
||||
}
|
||||
|
||||
FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining)
|
||||
:Encoder(_oggchaining),
|
||||
audio_format(_audio_format), fse(_fse),
|
||||
compression(_compression),
|
||||
oggflac(_oggflac)
|
||||
{
|
||||
/* this immediately outputs data through callback */
|
||||
|
||||
auto init_status = oggflac ?
|
||||
FLAC__stream_encoder_init_ogg_stream(fse,
|
||||
nullptr, WriteCallback,
|
||||
nullptr, nullptr, nullptr,
|
||||
this)
|
||||
:
|
||||
FLAC__stream_encoder_init_stream(fse,
|
||||
WriteCallback,
|
||||
nullptr, nullptr, nullptr,
|
||||
this);
|
||||
|
||||
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
||||
throw FmtRuntimeError("failed to initialize encoder: {}",
|
||||
FLAC__StreamEncoderInitStatusString[init_status]);
|
||||
}
|
||||
|
||||
Encoder *
|
||||
PreparedFlacEncoder::Open(AudioFormat &audio_format)
|
||||
{
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S8:
|
||||
break;
|
||||
|
||||
case SampleFormat::S16:
|
||||
break;
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
break;
|
||||
|
||||
default:
|
||||
audio_format.format = SampleFormat::S24_P32;
|
||||
}
|
||||
|
||||
/* allocate the encoder */
|
||||
auto fse = FLAC__stream_encoder_new();
|
||||
if (fse == nullptr)
|
||||
throw std::runtime_error("FLAC__stream_encoder_new() failed");
|
||||
|
||||
try {
|
||||
flac_encoder_setup(fse, compression, oggflac, audio_format);
|
||||
} catch (...) {
|
||||
FLAC__stream_encoder_delete(fse);
|
||||
throw;
|
||||
}
|
||||
|
||||
return new FlacEncoder(audio_format, fse, compression, oggflac, oggchaining);
|
||||
}
|
||||
|
||||
void
|
||||
FlacEncoder::SendTag(const Tag &tag)
|
||||
{
|
||||
/* re-initialize encoder since flac_encoder_finish resets everything */
|
||||
flac_encoder_setup(fse, compression, oggflac, audio_format);
|
||||
|
||||
FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
||||
FLAC__StreamMetadata_VorbisComment_Entry entry;
|
||||
|
||||
for (const auto &item : tag) {
|
||||
char name[64];
|
||||
ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
|
||||
FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, name, item.value);
|
||||
FLAC__metadata_object_vorbiscomment_append_comment(metadata, entry, false);
|
||||
}
|
||||
|
||||
FLAC__stream_encoder_set_metadata(fse,&metadata,1);
|
||||
|
||||
auto init_status = FLAC__stream_encoder_init_ogg_stream(fse,
|
||||
nullptr, WriteCallback,
|
||||
nullptr, nullptr, nullptr,
|
||||
this);
|
||||
|
||||
FLAC__metadata_object_delete(metadata);
|
||||
|
||||
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
||||
throw FmtRuntimeError("failed to initialize encoder: {}",
|
||||
FLAC__StreamEncoderInitStatusString[init_status]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static std::span<const FLAC__int32>
|
||||
ToFlac32(PcmBuffer &buffer, std::span<const T> src) noexcept
|
||||
{
|
||||
FLAC__int32 *dest = buffer.GetT<FLAC__int32>(src.size());
|
||||
std::copy(src.begin(), src.end(), dest);
|
||||
return {dest, src.size()};
|
||||
}
|
||||
|
||||
static std::span<const FLAC__int32>
|
||||
ToFlac32(PcmBuffer &buffer, std::span<const std::byte> src,
|
||||
SampleFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case SampleFormat::S8:
|
||||
return ToFlac32(buffer, FromBytesStrict<const int8_t>(src));
|
||||
|
||||
case SampleFormat::S16:
|
||||
return ToFlac32(buffer, FromBytesStrict<const int16_t>(src));
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
case SampleFormat::S32:
|
||||
/* nothing need to be done; format is the same for
|
||||
both mpd and libFLAC */
|
||||
return FromBytesStrict<const int32_t>(src);
|
||||
|
||||
default:
|
||||
std::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FlacEncoder::Write(std::span<const std::byte> src)
|
||||
{
|
||||
const auto imported = ToFlac32(expand_buffer, src,
|
||||
audio_format.format);
|
||||
const std::size_t n_frames = imported.size() / audio_format.channels;
|
||||
|
||||
/* feed samples to encoder */
|
||||
|
||||
if (!FLAC__stream_encoder_process_interleaved(fse, imported.data(),
|
||||
n_frames))
|
||||
throw std::runtime_error("flac encoder process failed");
|
||||
}
|
||||
|
||||
const EncoderPlugin flac_encoder_plugin = {
|
||||
"flac",
|
||||
flac_encoder_init,
|
||||
};
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ENCODER_FLAC_HXX
|
||||
#define MPD_ENCODER_FLAC_HXX
|
||||
|
||||
extern const struct EncoderPlugin flac_encoder_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,187 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "LameEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
#include "util/CNumberParser.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/SpanCast.hxx"
|
||||
|
||||
#include <lame/lame.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
class LameEncoder final : public Encoder {
|
||||
lame_global_flags *const gfp;
|
||||
|
||||
ReusableArray<std::byte, 32768> output_buffer;
|
||||
std::span<const std::byte> output{};
|
||||
|
||||
public:
|
||||
static constexpr unsigned CHANNELS = 2;
|
||||
|
||||
explicit LameEncoder(lame_global_flags *_gfp) noexcept
|
||||
:Encoder(false), gfp(_gfp) {}
|
||||
|
||||
~LameEncoder() noexcept override;
|
||||
|
||||
LameEncoder(const LameEncoder &) = delete;
|
||||
LameEncoder &operator=(const LameEncoder &) = delete;
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void Write(std::span<const std::byte> src) override;
|
||||
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override;
|
||||
};
|
||||
|
||||
class PreparedLameEncoder final : public PreparedEncoder {
|
||||
float quality;
|
||||
int bitrate;
|
||||
|
||||
public:
|
||||
explicit PreparedLameEncoder(const ConfigBlock &block);
|
||||
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &audio_format) override;
|
||||
|
||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||
return "audio/mpeg";
|
||||
}
|
||||
};
|
||||
|
||||
PreparedLameEncoder::PreparedLameEncoder(const ConfigBlock &block)
|
||||
{
|
||||
const char *value;
|
||||
char *endptr;
|
||||
|
||||
value = block.GetBlockValue("quality");
|
||||
if (value != nullptr) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
quality = float(ParseDouble(value, &endptr));
|
||||
|
||||
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
|
||||
throw FmtRuntimeError("quality {:?} is not a number in the "
|
||||
"range -1 to 10",
|
||||
value);
|
||||
|
||||
if (block.GetBlockValue("bitrate") != nullptr)
|
||||
throw std::runtime_error("quality and bitrate are both defined");
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
value = block.GetBlockValue("bitrate");
|
||||
if (value == nullptr)
|
||||
throw std::runtime_error("neither bitrate nor quality defined");
|
||||
|
||||
quality = -2.0;
|
||||
bitrate = ParseInt(value, &endptr);
|
||||
|
||||
if (*endptr != '\0' || bitrate <= 0)
|
||||
throw std::runtime_error("bitrate should be a positive integer");
|
||||
}
|
||||
}
|
||||
|
||||
static PreparedEncoder *
|
||||
lame_encoder_init(const ConfigBlock &block)
|
||||
{
|
||||
return new PreparedLameEncoder(block);
|
||||
}
|
||||
|
||||
static void
|
||||
lame_encoder_setup(lame_global_flags *gfp, float quality, int bitrate,
|
||||
const AudioFormat &audio_format)
|
||||
{
|
||||
if (quality >= -1.0f) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
if (0 != lame_set_VBR(gfp, vbr_rh))
|
||||
throw std::runtime_error("error setting lame VBR mode");
|
||||
|
||||
if (0 != lame_set_VBR_q(gfp, int(quality)))
|
||||
throw std::runtime_error("error setting lame VBR quality");
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
if (0 != lame_set_brate(gfp, bitrate))
|
||||
throw std::runtime_error("error setting lame bitrate");
|
||||
}
|
||||
|
||||
if (0 != lame_set_num_channels(gfp, audio_format.channels))
|
||||
throw std::runtime_error("error setting lame num channels");
|
||||
|
||||
if (0 != lame_set_in_samplerate(gfp, audio_format.sample_rate))
|
||||
throw std::runtime_error("error setting lame sample rate");
|
||||
|
||||
if (0 != lame_set_out_samplerate(gfp, audio_format.sample_rate))
|
||||
throw std::runtime_error("error setting lame out sample rate");
|
||||
|
||||
if (0 > lame_init_params(gfp))
|
||||
throw std::runtime_error("error initializing lame params");
|
||||
}
|
||||
|
||||
Encoder *
|
||||
PreparedLameEncoder::Open(AudioFormat &audio_format)
|
||||
{
|
||||
audio_format.format = SampleFormat::S16;
|
||||
audio_format.channels = LameEncoder::CHANNELS;
|
||||
|
||||
auto gfp = lame_init();
|
||||
if (gfp == nullptr)
|
||||
throw std::runtime_error("lame_init() failed");
|
||||
|
||||
try {
|
||||
lame_encoder_setup(gfp, quality, bitrate, audio_format);
|
||||
} catch (...) {
|
||||
lame_close(gfp);
|
||||
throw;
|
||||
}
|
||||
|
||||
return new LameEncoder(gfp);
|
||||
}
|
||||
|
||||
LameEncoder::~LameEncoder() noexcept
|
||||
{
|
||||
lame_close(gfp);
|
||||
}
|
||||
|
||||
void
|
||||
LameEncoder::Write(std::span<const std::byte> _src)
|
||||
{
|
||||
const auto src = FromBytesStrict<const int16_t>(_src);
|
||||
|
||||
assert(output.empty());
|
||||
|
||||
const std::size_t num_samples = src.size();
|
||||
const std::size_t num_frames = num_samples / CHANNELS;
|
||||
|
||||
/* worst-case formula according to LAME documentation */
|
||||
const std::size_t output_buffer_size = 5 * num_samples / 4 + 7200;
|
||||
const auto dest = output_buffer.Get(output_buffer_size);
|
||||
|
||||
/* this is for only 16-bit audio */
|
||||
|
||||
int bytes_out = lame_encode_buffer_interleaved(gfp,
|
||||
const_cast<short *>(src.data()),
|
||||
num_frames,
|
||||
(unsigned char *)dest,
|
||||
output_buffer_size);
|
||||
|
||||
if (bytes_out < 0)
|
||||
throw std::runtime_error("lame encoder failed");
|
||||
|
||||
output = {dest, std::size_t(bytes_out)};
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
LameEncoder::Read(std::span<std::byte>) noexcept
|
||||
{
|
||||
return std::exchange(output, std::span<const std::byte>{});
|
||||
}
|
||||
|
||||
const EncoderPlugin lame_encoder_plugin = {
|
||||
"lame",
|
||||
lame_encoder_init,
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ENCODER_LAME_HXX
|
||||
#define MPD_ENCODER_LAME_HXX
|
||||
|
||||
extern const struct EncoderPlugin lame_encoder_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,42 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "NullEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
|
||||
class NullEncoder final : public Encoder {
|
||||
DynamicFifoBuffer<std::byte> buffer{8192};
|
||||
|
||||
public:
|
||||
NullEncoder()
|
||||
:Encoder(false) {}
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void Write(std::span<const std::byte> src) override {
|
||||
buffer.Append(src);
|
||||
}
|
||||
|
||||
std::span<const std::byte> Read(std::span<std::byte> b) noexcept override {
|
||||
return b.first(buffer.Read(b.data(), b.size()));
|
||||
}
|
||||
};
|
||||
|
||||
class PreparedNullEncoder final : public PreparedEncoder {
|
||||
public:
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &) override {
|
||||
return new NullEncoder();
|
||||
}
|
||||
};
|
||||
|
||||
static PreparedEncoder *
|
||||
null_encoder_init([[maybe_unused]] const ConfigBlock &block)
|
||||
{
|
||||
return new PreparedNullEncoder();
|
||||
}
|
||||
|
||||
const EncoderPlugin null_encoder_plugin = {
|
||||
"null",
|
||||
null_encoder_init,
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ENCODER_NULL_HXX
|
||||
#define MPD_ENCODER_NULL_HXX
|
||||
|
||||
extern const struct EncoderPlugin null_encoder_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,55 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_OGG_ENCODER_HXX
|
||||
#define MPD_OGG_ENCODER_HXX
|
||||
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "lib/xiph/OggStreamState.hxx"
|
||||
#include "lib/xiph/OggPage.hxx"
|
||||
#include "util/Serial.hxx"
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
/**
|
||||
* An abstract base class which contains code common to all encoders
|
||||
* with Ogg container output.
|
||||
*/
|
||||
class OggEncoder : public Encoder {
|
||||
/* initialize "flush" to true, so the caller gets the full
|
||||
headers on the first read */
|
||||
bool flush = true;
|
||||
|
||||
protected:
|
||||
OggStreamState stream;
|
||||
|
||||
public:
|
||||
OggEncoder(bool _implements_tag)
|
||||
:Encoder(_implements_tag),
|
||||
stream(GenerateSerial()) {
|
||||
}
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void Flush() final {
|
||||
flush = true;
|
||||
}
|
||||
|
||||
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override {
|
||||
ogg_page page;
|
||||
bool success = stream.PageOut(page);
|
||||
if (!success) {
|
||||
if (flush) {
|
||||
flush = false;
|
||||
success = stream.Flush(page);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
return {};
|
||||
}
|
||||
|
||||
return buffer.first(ReadPage(page, buffer.data(),
|
||||
buffer.size()));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,402 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "OpusEncoderPlugin.hxx"
|
||||
#include "OggEncoder.hxx"
|
||||
#include "tag/Names.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
|
||||
#include <opus.h>
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class OpusEncoder final : public OggEncoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
const size_t frame_size;
|
||||
|
||||
const size_t buffer_frames, buffer_size;
|
||||
size_t buffer_position = 0;
|
||||
std::byte *const buffer;
|
||||
|
||||
::OpusEncoder *const enc;
|
||||
|
||||
unsigned char buffer2[1275 * 3 + 7];
|
||||
|
||||
int lookahead;
|
||||
|
||||
ogg_int64_t packetno = 0;
|
||||
|
||||
ogg_int64_t granulepos = 0;
|
||||
|
||||
public:
|
||||
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc, bool _chaining);
|
||||
~OpusEncoder() noexcept override;
|
||||
|
||||
OpusEncoder(const OpusEncoder &) = delete;
|
||||
OpusEncoder &operator=(const OpusEncoder &) = delete;
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void End() override;
|
||||
void Write(std::span<const std::byte> src) override;
|
||||
|
||||
void PreTag() override;
|
||||
void SendTag(const Tag &tag) override;
|
||||
|
||||
private:
|
||||
void DoEncode(bool eos);
|
||||
void WriteSilence(unsigned fill_frames);
|
||||
|
||||
void GenerateHeaders(const Tag *tag) noexcept;
|
||||
void GenerateHead() noexcept;
|
||||
void GenerateTags(const Tag *tag) noexcept;
|
||||
};
|
||||
|
||||
class PreparedOpusEncoder final : public PreparedEncoder {
|
||||
opus_int32 bitrate;
|
||||
int complexity;
|
||||
int signal;
|
||||
int packet_loss;
|
||||
int vbr;
|
||||
int vbr_constraint;
|
||||
const bool chaining;
|
||||
|
||||
public:
|
||||
explicit PreparedOpusEncoder(const ConfigBlock &block);
|
||||
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &audio_format) override;
|
||||
|
||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||
return "audio/ogg";
|
||||
}
|
||||
};
|
||||
|
||||
PreparedOpusEncoder::PreparedOpusEncoder(const ConfigBlock &block)
|
||||
:chaining(block.GetBlockValue("opustags", false))
|
||||
{
|
||||
const char *value = block.GetBlockValue("bitrate", "auto");
|
||||
if (strcmp(value, "auto") == 0)
|
||||
bitrate = OPUS_AUTO;
|
||||
else if (strcmp(value, "max") == 0)
|
||||
bitrate = OPUS_BITRATE_MAX;
|
||||
else {
|
||||
char *endptr;
|
||||
bitrate = strtoul(value, &endptr, 10);
|
||||
if (endptr == value || *endptr != 0 ||
|
||||
bitrate < 500 || bitrate > 512000)
|
||||
throw std::runtime_error("Invalid bit rate");
|
||||
}
|
||||
|
||||
complexity = block.GetBlockValue("complexity", 10U);
|
||||
if (complexity > 10)
|
||||
throw std::runtime_error("Invalid complexity");
|
||||
|
||||
value = block.GetBlockValue("signal", "auto");
|
||||
if (strcmp(value, "auto") == 0)
|
||||
signal = OPUS_AUTO;
|
||||
else if (strcmp(value, "voice") == 0)
|
||||
signal = OPUS_SIGNAL_VOICE;
|
||||
else if (strcmp(value, "music") == 0)
|
||||
signal = OPUS_SIGNAL_MUSIC;
|
||||
else
|
||||
throw std::runtime_error("Invalid signal");
|
||||
|
||||
value = block.GetBlockValue("vbr", "yes");
|
||||
if (strcmp(value, "yes") == 0) {
|
||||
vbr = 1U;
|
||||
vbr_constraint = 0U;
|
||||
} else if (strcmp(value, "no") == 0) {
|
||||
vbr = 0U;
|
||||
vbr_constraint = 0U;
|
||||
} else if (strcmp(value, "constrained") == 0) {
|
||||
vbr = 1U;
|
||||
vbr_constraint = 1U;
|
||||
} else
|
||||
throw std::runtime_error("Invalid vbr");
|
||||
|
||||
packet_loss = block.GetBlockValue("packet_loss", 0U);
|
||||
if (packet_loss > 100)
|
||||
throw std::runtime_error("Invalid packet loss");
|
||||
}
|
||||
|
||||
PreparedEncoder *
|
||||
opus_encoder_init(const ConfigBlock &block)
|
||||
{
|
||||
return new PreparedOpusEncoder(block);
|
||||
}
|
||||
|
||||
OpusEncoder::OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc, bool _chaining)
|
||||
:OggEncoder(_chaining),
|
||||
audio_format(_audio_format),
|
||||
frame_size(_audio_format.GetFrameSize()),
|
||||
buffer_frames(_audio_format.sample_rate / 50),
|
||||
buffer_size(frame_size * buffer_frames),
|
||||
buffer(new std::byte[buffer_size]),
|
||||
enc(_enc)
|
||||
{
|
||||
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
|
||||
GenerateHeaders(nullptr);
|
||||
}
|
||||
|
||||
Encoder *
|
||||
PreparedOpusEncoder::Open(AudioFormat &audio_format)
|
||||
{
|
||||
/* libopus supports only 48 kHz */
|
||||
audio_format.sample_rate = 48000;
|
||||
|
||||
if (audio_format.channels > 2)
|
||||
audio_format.channels = 1;
|
||||
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S16:
|
||||
case SampleFormat::FLOAT:
|
||||
break;
|
||||
|
||||
case SampleFormat::S8:
|
||||
audio_format.format = SampleFormat::S16;
|
||||
break;
|
||||
|
||||
default:
|
||||
audio_format.format = SampleFormat::FLOAT;
|
||||
break;
|
||||
}
|
||||
|
||||
int error_code;
|
||||
auto *enc = opus_encoder_create(audio_format.sample_rate,
|
||||
audio_format.channels,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
&error_code);
|
||||
if (enc == nullptr)
|
||||
throw std::runtime_error(opus_strerror(error_code));
|
||||
|
||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
|
||||
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
|
||||
opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal));
|
||||
opus_encoder_ctl(enc, OPUS_SET_VBR(vbr));
|
||||
opus_encoder_ctl(enc, OPUS_SET_VBR_CONSTRAINT(vbr_constraint));
|
||||
opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(packet_loss));
|
||||
|
||||
return new OpusEncoder(audio_format, enc, chaining);
|
||||
}
|
||||
|
||||
OpusEncoder::~OpusEncoder() noexcept
|
||||
{
|
||||
delete[] buffer;
|
||||
opus_encoder_destroy(enc);
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::DoEncode(bool eos)
|
||||
{
|
||||
assert(buffer_position == buffer_size || eos);
|
||||
|
||||
opus_int32 result =
|
||||
audio_format.format == SampleFormat::S16
|
||||
? opus_encode(enc,
|
||||
(const opus_int16 *)buffer,
|
||||
buffer_frames,
|
||||
buffer2,
|
||||
sizeof(buffer2))
|
||||
: opus_encode_float(enc,
|
||||
(const float *)buffer,
|
||||
buffer_frames,
|
||||
buffer2,
|
||||
sizeof(buffer2));
|
||||
if (result < 0)
|
||||
throw std::runtime_error("Opus encoder error");
|
||||
|
||||
granulepos += buffer_position / frame_size;
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = buffer2;
|
||||
packet.bytes = result;
|
||||
packet.b_o_s = false;
|
||||
packet.e_o_s = eos;
|
||||
packet.granulepos = granulepos;
|
||||
packet.packetno = packetno++;
|
||||
stream.PacketIn(packet);
|
||||
|
||||
buffer_position = 0;
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::End()
|
||||
{
|
||||
memset(buffer + buffer_position, 0,
|
||||
buffer_size - buffer_position);
|
||||
DoEncode(true);
|
||||
Flush();
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::WriteSilence(unsigned fill_frames)
|
||||
{
|
||||
size_t fill_bytes = fill_frames * frame_size;
|
||||
|
||||
while (fill_bytes > 0) {
|
||||
size_t nbytes = buffer_size - buffer_position;
|
||||
if (nbytes > fill_bytes)
|
||||
nbytes = fill_bytes;
|
||||
|
||||
memset(buffer + buffer_position, 0, nbytes);
|
||||
buffer_position += nbytes;
|
||||
fill_bytes -= nbytes;
|
||||
|
||||
if (buffer_position == buffer_size)
|
||||
DoEncode(false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::Write(std::span<const std::byte> src)
|
||||
{
|
||||
if (lookahead > 0) {
|
||||
/* generate some silence at the beginning of the
|
||||
stream */
|
||||
|
||||
assert(buffer_position == 0);
|
||||
|
||||
WriteSilence(lookahead);
|
||||
lookahead = 0;
|
||||
}
|
||||
|
||||
while (!src.empty()) {
|
||||
const std::size_t nbytes = std::min(buffer_size - buffer_position,
|
||||
src.size());
|
||||
|
||||
memcpy(buffer + buffer_position, src.data(), nbytes);
|
||||
src = src.subspan(nbytes);
|
||||
buffer_position += nbytes;
|
||||
|
||||
if (buffer_position == buffer_size)
|
||||
DoEncode(false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::GenerateHeaders(const Tag *tag) noexcept
|
||||
{
|
||||
GenerateHead();
|
||||
GenerateTags(tag);
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::GenerateHead() noexcept
|
||||
{
|
||||
unsigned char header[19];
|
||||
memcpy(header, "OpusHead", 8);
|
||||
header[8] = 1;
|
||||
header[9] = audio_format.channels;
|
||||
*(uint16_t *)(header + 10) = ToLE16(lookahead);
|
||||
*(uint32_t *)(header + 12) = ToLE32(audio_format.sample_rate);
|
||||
header[16] = 0;
|
||||
header[17] = 0;
|
||||
header[18] = 0;
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = header;
|
||||
packet.bytes = sizeof(header);
|
||||
packet.b_o_s = true;
|
||||
packet.e_o_s = false;
|
||||
packet.granulepos = 0;
|
||||
packet.packetno = packetno++;
|
||||
stream.PacketIn(packet);
|
||||
// flush not needed because libogg autoflushes on b_o_s flag
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::GenerateTags(const Tag *tag) noexcept
|
||||
{
|
||||
const char *version = opus_get_version_string();
|
||||
size_t version_length = strlen(version);
|
||||
|
||||
// len("OpusTags") + 4 byte version length + len(version) + 4 byte tag count
|
||||
size_t comments_size = 8 + 4 + version_length + 4;
|
||||
uint32_t tag_count = 0;
|
||||
if (tag) {
|
||||
for (const auto &item: *tag) {
|
||||
++tag_count;
|
||||
// 4 byte length + len(tagname) + len('=') + len(value)
|
||||
comments_size += 4 + strlen(tag_item_names[item.type]) + 1 + strlen(item.value);
|
||||
}
|
||||
}
|
||||
|
||||
auto *comments = new unsigned char[comments_size];
|
||||
unsigned char *p = comments;
|
||||
|
||||
memcpy(comments, "OpusTags", 8);
|
||||
*(uint32_t *)(comments + 8) = ToLE32(version_length);
|
||||
p += 12;
|
||||
|
||||
memcpy(p, version, version_length);
|
||||
p += version_length;
|
||||
|
||||
tag_count = ToLE32(tag_count);
|
||||
memcpy(p, &tag_count, 4);
|
||||
p += 4;
|
||||
|
||||
if (tag) {
|
||||
for (const auto &item: *tag) {
|
||||
size_t tag_name_len = strlen(tag_item_names[item.type]);
|
||||
size_t tag_val_len = strlen(item.value);
|
||||
uint32_t tag_len_le = ToLE32(tag_name_len + 1 + tag_val_len);
|
||||
|
||||
memcpy(p, &tag_len_le, 4);
|
||||
p += 4;
|
||||
|
||||
ToUpperASCII((char *)p, tag_item_names[item.type], tag_name_len + 1);
|
||||
p += tag_name_len;
|
||||
|
||||
*p++ = '=';
|
||||
|
||||
memcpy(p, item.value, tag_val_len);
|
||||
p += tag_val_len;
|
||||
}
|
||||
}
|
||||
assert(comments + comments_size == p);
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = comments;
|
||||
packet.bytes = comments_size;
|
||||
packet.b_o_s = false;
|
||||
packet.e_o_s = false;
|
||||
packet.granulepos = 0;
|
||||
packet.packetno = packetno++;
|
||||
stream.PacketIn(packet);
|
||||
Flush();
|
||||
|
||||
delete[] comments;
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::PreTag()
|
||||
{
|
||||
End();
|
||||
packetno = 0;
|
||||
granulepos = 0; // not really required, but useful to prevent wraparound
|
||||
opus_encoder_ctl(enc, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::SendTag(const Tag &tag)
|
||||
{
|
||||
stream.Reinitialize(GenerateSerial());
|
||||
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
|
||||
GenerateHeaders(&tag);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const EncoderPlugin opus_encoder_plugin = {
|
||||
"opus",
|
||||
opus_encoder_init,
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ENCODER_OPUS_H
|
||||
#define MPD_ENCODER_OPUS_H
|
||||
|
||||
extern const struct EncoderPlugin opus_encoder_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,193 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "ShineEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
#include "util/SpanCast.hxx"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <shine/layer3.h>
|
||||
}
|
||||
|
||||
static constexpr size_t BUFFER_INIT_SIZE = 8192;
|
||||
static constexpr unsigned CHANNELS = 2;
|
||||
|
||||
class ShineEncoder final : public Encoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
const shine_t shine;
|
||||
|
||||
const size_t frame_size;
|
||||
|
||||
/* workaround for bug:
|
||||
https://github.com/savonet/shine/issues/11 */
|
||||
size_t input_pos = SHINE_MAX_SAMPLES + 1;
|
||||
|
||||
int16_t *stereo[CHANNELS];
|
||||
|
||||
DynamicFifoBuffer<std::byte> output_buffer{BUFFER_INIT_SIZE};
|
||||
|
||||
public:
|
||||
ShineEncoder(AudioFormat _audio_format, shine_t _shine) noexcept
|
||||
:Encoder(false),
|
||||
audio_format(_audio_format), shine(_shine),
|
||||
frame_size(shine_samples_per_pass(shine)),
|
||||
stereo{new int16_t[frame_size], new int16_t[frame_size]}
|
||||
{}
|
||||
|
||||
~ShineEncoder() noexcept override {
|
||||
if (input_pos > SHINE_MAX_SAMPLES) {
|
||||
/* write zero chunk */
|
||||
input_pos = 0;
|
||||
WriteChunk(true);
|
||||
}
|
||||
|
||||
shine_close(shine);
|
||||
delete[] stereo[0];
|
||||
delete[] stereo[1];
|
||||
}
|
||||
|
||||
bool WriteChunk(bool flush);
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void End() override {
|
||||
return Flush();
|
||||
}
|
||||
|
||||
void Flush() override;
|
||||
|
||||
void Write(std::span<const std::byte> src) override;
|
||||
|
||||
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override {
|
||||
return buffer.first(output_buffer.Read(buffer.data(), buffer.size()));
|
||||
}
|
||||
};
|
||||
|
||||
class PreparedShineEncoder final : public PreparedEncoder {
|
||||
shine_config_t config;
|
||||
|
||||
public:
|
||||
explicit PreparedShineEncoder(const ConfigBlock &block);
|
||||
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &audio_format) override;
|
||||
|
||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||
return "audio/mpeg";
|
||||
}
|
||||
};
|
||||
|
||||
PreparedShineEncoder::PreparedShineEncoder(const ConfigBlock &block)
|
||||
{
|
||||
shine_set_config_mpeg_defaults(&config.mpeg);
|
||||
config.mpeg.bitr = block.GetBlockValue("bitrate", 128);
|
||||
}
|
||||
|
||||
static PreparedEncoder *
|
||||
shine_encoder_init(const ConfigBlock &block)
|
||||
{
|
||||
return new PreparedShineEncoder(block);
|
||||
}
|
||||
|
||||
static shine_t
|
||||
SetupShine(shine_config_t config, AudioFormat &audio_format)
|
||||
{
|
||||
audio_format.format = SampleFormat::S16;
|
||||
audio_format.channels = CHANNELS;
|
||||
|
||||
config.mpeg.mode = audio_format.channels == 2 ? STEREO : MONO;
|
||||
config.wave.samplerate = audio_format.sample_rate;
|
||||
config.wave.channels =
|
||||
audio_format.channels == 2 ? PCM_STEREO : PCM_MONO;
|
||||
|
||||
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0)
|
||||
throw FmtRuntimeError("error configuring shine. "
|
||||
"samplerate {} and bitrate {} configuration"
|
||||
" not supported.",
|
||||
config.wave.samplerate,
|
||||
config.mpeg.bitr);
|
||||
|
||||
auto shine = shine_initialise(&config);
|
||||
if (!shine)
|
||||
throw std::runtime_error("error initializing shine");
|
||||
|
||||
return shine;
|
||||
}
|
||||
|
||||
Encoder *
|
||||
PreparedShineEncoder::Open(AudioFormat &audio_format)
|
||||
{
|
||||
auto shine = SetupShine(config, audio_format);
|
||||
return new ShineEncoder(audio_format, shine);
|
||||
}
|
||||
|
||||
bool
|
||||
ShineEncoder::WriteChunk(bool flush)
|
||||
{
|
||||
if (flush || input_pos == frame_size) {
|
||||
if (flush) {
|
||||
/* fill remaining with 0s */
|
||||
for (; input_pos < frame_size; input_pos++) {
|
||||
stereo[0][input_pos] = stereo[1][input_pos] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int written;
|
||||
const auto out = (const std::byte *)
|
||||
shine_encode_buffer(shine, stereo, &written);
|
||||
|
||||
if (written > 0)
|
||||
output_buffer.Append({out, std::size_t(written)});
|
||||
|
||||
input_pos = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ShineEncoder::Write(std::span<const std::byte> _src)
|
||||
{
|
||||
const auto src = FromBytesStrict<const int16_t>(_src);
|
||||
const std::size_t nframes = src.size() / audio_format.channels;
|
||||
size_t written = 0;
|
||||
|
||||
if (input_pos > SHINE_MAX_SAMPLES)
|
||||
input_pos = 0;
|
||||
|
||||
/* write all data to de-interleaved buffers */
|
||||
while (written < nframes) {
|
||||
for (;
|
||||
written < nframes && input_pos < frame_size;
|
||||
written++, input_pos++) {
|
||||
const size_t base =
|
||||
written * audio_format.channels;
|
||||
stereo[0][input_pos] = src[base];
|
||||
stereo[1][input_pos] = src[base + 1];
|
||||
}
|
||||
/* write if chunk is filled */
|
||||
WriteChunk(false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ShineEncoder::Flush()
|
||||
{
|
||||
/* flush buffers and flush shine */
|
||||
WriteChunk(true);
|
||||
|
||||
int written;
|
||||
const auto data = (const std::byte *)shine_flush(shine, &written);
|
||||
|
||||
if (written > 0)
|
||||
output_buffer.Append({data, std::size_t(written)});
|
||||
}
|
||||
|
||||
const EncoderPlugin shine_encoder_plugin = {
|
||||
"shine",
|
||||
shine_encoder_init,
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ENCODER_SHINE_HXX
|
||||
#define MPD_ENCODER_SHINE_HXX
|
||||
|
||||
extern const struct EncoderPlugin shine_encoder_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,209 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "TwolameEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
#include "util/CNumberParser.hxx"
|
||||
#include "util/SpanCast.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <twolame.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
class TwolameEncoder final : public Encoder {
|
||||
twolame_options *options;
|
||||
|
||||
std::byte output_buffer[32768];
|
||||
std::size_t fill = 0;
|
||||
|
||||
/**
|
||||
* Call libtwolame's flush function when the output_buffer is
|
||||
* empty?
|
||||
*/
|
||||
bool flush = false;
|
||||
|
||||
public:
|
||||
static constexpr unsigned CHANNELS = 2;
|
||||
|
||||
explicit TwolameEncoder(twolame_options *_options) noexcept
|
||||
:Encoder(false), options(_options) {}
|
||||
~TwolameEncoder() noexcept override;
|
||||
|
||||
TwolameEncoder(const TwolameEncoder &) = delete;
|
||||
TwolameEncoder &operator=(const TwolameEncoder &) = delete;
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
|
||||
void End() override {
|
||||
flush = true;
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
flush = true;
|
||||
}
|
||||
|
||||
void Write(std::span<const std::byte> src) override;
|
||||
std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override;
|
||||
};
|
||||
|
||||
class PreparedTwolameEncoder final : public PreparedEncoder {
|
||||
float quality;
|
||||
int bitrate;
|
||||
|
||||
public:
|
||||
explicit PreparedTwolameEncoder(const ConfigBlock &block);
|
||||
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &audio_format) override;
|
||||
|
||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||
return "audio/mpeg";
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr Domain twolame_encoder_domain("twolame_encoder");
|
||||
|
||||
PreparedTwolameEncoder::PreparedTwolameEncoder(const ConfigBlock &block)
|
||||
{
|
||||
const char *value;
|
||||
char *endptr;
|
||||
|
||||
value = block.GetBlockValue("quality");
|
||||
if (value != nullptr) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
quality = float(ParseDouble(value, &endptr));
|
||||
|
||||
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
|
||||
throw FmtRuntimeError("quality {:?} is not a number in the "
|
||||
"range -1 to 10",
|
||||
value);
|
||||
|
||||
if (block.GetBlockValue("bitrate") != nullptr)
|
||||
throw std::runtime_error("quality and bitrate are both defined");
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
value = block.GetBlockValue("bitrate");
|
||||
if (value == nullptr)
|
||||
throw std::runtime_error("neither bitrate nor quality defined");
|
||||
|
||||
quality = -2.0;
|
||||
bitrate = ParseInt(value, &endptr);
|
||||
|
||||
if (*endptr != '\0' || bitrate <= 0)
|
||||
throw std::runtime_error("bitrate should be a positive integer");
|
||||
}
|
||||
}
|
||||
|
||||
static PreparedEncoder *
|
||||
twolame_encoder_init(const ConfigBlock &block)
|
||||
{
|
||||
FmtDebug(twolame_encoder_domain,
|
||||
"libtwolame version {}", get_twolame_version());
|
||||
|
||||
return new PreparedTwolameEncoder(block);
|
||||
}
|
||||
|
||||
static void
|
||||
twolame_encoder_setup(twolame_options *options, float quality, int bitrate,
|
||||
const AudioFormat &audio_format)
|
||||
{
|
||||
if (quality >= -1.0f) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
if (0 != twolame_set_VBR(options, true))
|
||||
throw std::runtime_error("error setting twolame VBR mode");
|
||||
|
||||
if (0 != twolame_set_VBR_q(options, quality))
|
||||
throw std::runtime_error("error setting twolame VBR quality");
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
if (0 != twolame_set_brate(options, bitrate))
|
||||
throw std::runtime_error("error setting twolame bitrate");
|
||||
}
|
||||
|
||||
if (0 != twolame_set_num_channels(options, audio_format.channels))
|
||||
throw std::runtime_error("error setting twolame num channels");
|
||||
|
||||
if (0 != twolame_set_in_samplerate(options,
|
||||
audio_format.sample_rate))
|
||||
throw std::runtime_error("error setting twolame sample rate");
|
||||
|
||||
if (0 > twolame_init_params(options))
|
||||
throw std::runtime_error("error initializing twolame params");
|
||||
}
|
||||
|
||||
Encoder *
|
||||
PreparedTwolameEncoder::Open(AudioFormat &audio_format)
|
||||
{
|
||||
audio_format.format = SampleFormat::S16;
|
||||
audio_format.channels = TwolameEncoder::CHANNELS;
|
||||
|
||||
auto options = twolame_init();
|
||||
if (options == nullptr)
|
||||
throw std::runtime_error("twolame_init() failed");
|
||||
|
||||
try {
|
||||
twolame_encoder_setup(options, quality, bitrate,
|
||||
audio_format);
|
||||
} catch (...) {
|
||||
twolame_close(&options);
|
||||
throw;
|
||||
}
|
||||
|
||||
return new TwolameEncoder(options);
|
||||
}
|
||||
|
||||
TwolameEncoder::~TwolameEncoder() noexcept
|
||||
{
|
||||
twolame_close(&options);
|
||||
}
|
||||
|
||||
void
|
||||
TwolameEncoder::Write(std::span<const std::byte> _src)
|
||||
{
|
||||
const auto src = FromBytesStrict<const int16_t>(_src);
|
||||
|
||||
assert(fill == 0);
|
||||
|
||||
const std::size_t num_frames = src.size() / CHANNELS;
|
||||
|
||||
int bytes_out = twolame_encode_buffer_interleaved(options,
|
||||
src.data(), num_frames,
|
||||
(unsigned char *)output_buffer,
|
||||
sizeof(output_buffer));
|
||||
if (bytes_out < 0)
|
||||
throw std::runtime_error("twolame encoder failed");
|
||||
|
||||
fill = (std::size_t)bytes_out;
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
TwolameEncoder::Read(std::span<std::byte>) noexcept
|
||||
{
|
||||
assert(fill <= sizeof(output_buffer));
|
||||
|
||||
if (fill == 0 && flush) {
|
||||
int ret = twolame_encode_flush(options,
|
||||
(unsigned char *)output_buffer,
|
||||
sizeof(output_buffer));
|
||||
if (ret > 0)
|
||||
fill = (std::size_t)ret;
|
||||
|
||||
flush = false;
|
||||
}
|
||||
|
||||
return std::span{output_buffer}.first(std::exchange(fill, 0));
|
||||
}
|
||||
|
||||
const EncoderPlugin twolame_encoder_plugin = {
|
||||
"twolame",
|
||||
twolame_encoder_init,
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ENCODER_TWOLAME_HXX
|
||||
#define MPD_ENCODER_TWOLAME_HXX
|
||||
|
||||
extern const struct EncoderPlugin twolame_encoder_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,251 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "VorbisEncoderPlugin.hxx"
|
||||
#include "OggEncoder.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
#include "lib/xiph/VorbisComment.hxx"
|
||||
#include "tag/Names.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "config/Domain.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
#include "util/CNumberParser.hxx"
|
||||
|
||||
#include <vorbis/vorbisenc.h>
|
||||
|
||||
class VorbisEncoder final : public OggEncoder {
|
||||
AudioFormat audio_format;
|
||||
|
||||
vorbis_dsp_state vd;
|
||||
vorbis_block vb;
|
||||
vorbis_info vi;
|
||||
|
||||
public:
|
||||
VorbisEncoder(float quality, int bitrate, AudioFormat &_audio_format);
|
||||
|
||||
~VorbisEncoder() noexcept override {
|
||||
vorbis_block_clear(&vb);
|
||||
vorbis_dsp_clear(&vd);
|
||||
vorbis_info_clear(&vi);
|
||||
}
|
||||
|
||||
VorbisEncoder(const VorbisEncoder &) = delete;
|
||||
VorbisEncoder &operator=(const VorbisEncoder &) = delete;
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void End() override {
|
||||
PreTag();
|
||||
}
|
||||
|
||||
void PreTag() override;
|
||||
void SendTag(const Tag &tag) override;
|
||||
|
||||
void Write(std::span<const std::byte> src) override;
|
||||
|
||||
private:
|
||||
void HeaderOut(vorbis_comment &vc);
|
||||
void SendHeader();
|
||||
void BlockOut();
|
||||
};
|
||||
|
||||
class PreparedVorbisEncoder final : public PreparedEncoder {
|
||||
float quality = 3;
|
||||
int bitrate;
|
||||
|
||||
public:
|
||||
explicit PreparedVorbisEncoder(const ConfigBlock &block);
|
||||
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &audio_format) override;
|
||||
|
||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||
return "audio/ogg";
|
||||
}
|
||||
};
|
||||
|
||||
PreparedVorbisEncoder::PreparedVorbisEncoder(const ConfigBlock &block)
|
||||
{
|
||||
const char *value = block.GetBlockValue("quality");
|
||||
if (value != nullptr) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
char *endptr;
|
||||
quality = ParseDouble(value, &endptr);
|
||||
|
||||
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
|
||||
throw FmtRuntimeError("quality {:?} is not a number in the "
|
||||
"range -1 to 10",
|
||||
value);
|
||||
|
||||
if (block.GetBlockValue("bitrate") != nullptr)
|
||||
throw std::runtime_error("quality and bitrate are both defined");
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
value = block.GetBlockValue("bitrate");
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
quality = -2.0;
|
||||
|
||||
char *endptr;
|
||||
bitrate = ParseInt(value, &endptr);
|
||||
if (*endptr != '\0' || bitrate <= 0)
|
||||
throw std::runtime_error("bitrate should be a positive integer");
|
||||
}
|
||||
}
|
||||
|
||||
static PreparedEncoder *
|
||||
vorbis_encoder_init(const ConfigBlock &block)
|
||||
{
|
||||
return new PreparedVorbisEncoder(block);
|
||||
}
|
||||
|
||||
VorbisEncoder::VorbisEncoder(float quality, int bitrate,
|
||||
AudioFormat &_audio_format)
|
||||
:OggEncoder(true)
|
||||
{
|
||||
vorbis_info_init(&vi);
|
||||
|
||||
_audio_format.format = SampleFormat::FLOAT;
|
||||
audio_format = _audio_format;
|
||||
|
||||
if (quality >= -1.0f) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
if (0 != vorbis_encode_init_vbr(&vi,
|
||||
audio_format.channels,
|
||||
audio_format.sample_rate,
|
||||
quality * 0.1f)) {
|
||||
vorbis_info_clear(&vi);
|
||||
throw std::runtime_error("error initializing vorbis vbr");
|
||||
}
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
if (0 != vorbis_encode_init(&vi,
|
||||
audio_format.channels,
|
||||
audio_format.sample_rate, -1.0,
|
||||
bitrate * 1000, -1.0f)) {
|
||||
vorbis_info_clear(&vi);
|
||||
throw std::runtime_error("error initializing vorbis encoder");
|
||||
}
|
||||
}
|
||||
|
||||
vorbis_analysis_init(&vd, &vi);
|
||||
vorbis_block_init(&vd, &vb);
|
||||
|
||||
SendHeader();
|
||||
}
|
||||
|
||||
void
|
||||
VorbisEncoder::HeaderOut(vorbis_comment &vc)
|
||||
{
|
||||
ogg_packet packet, comments, codebooks;
|
||||
|
||||
vorbis_analysis_headerout(&vd, &vc,
|
||||
&packet, &comments, &codebooks);
|
||||
|
||||
stream.PacketIn(packet);
|
||||
stream.PacketIn(comments);
|
||||
stream.PacketIn(codebooks);
|
||||
}
|
||||
|
||||
void
|
||||
VorbisEncoder::SendHeader()
|
||||
{
|
||||
VorbisComment vc;
|
||||
HeaderOut(vc);
|
||||
}
|
||||
|
||||
Encoder *
|
||||
PreparedVorbisEncoder::Open(AudioFormat &audio_format)
|
||||
{
|
||||
return new VorbisEncoder(quality, bitrate, audio_format);
|
||||
}
|
||||
|
||||
void
|
||||
VorbisEncoder::BlockOut()
|
||||
{
|
||||
while (vorbis_analysis_blockout(&vd, &vb) == 1) {
|
||||
vorbis_analysis(&vb, nullptr);
|
||||
vorbis_bitrate_addblock(&vb);
|
||||
|
||||
ogg_packet packet;
|
||||
while (vorbis_bitrate_flushpacket(&vd, &packet))
|
||||
stream.PacketIn(packet);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VorbisEncoder::PreTag()
|
||||
{
|
||||
vorbis_analysis_wrote(&vd, 0);
|
||||
BlockOut();
|
||||
|
||||
/* reinitialize vorbis_dsp_state and vorbis_block to reset the
|
||||
end-of-stream marker */
|
||||
vorbis_block_clear(&vb);
|
||||
vorbis_dsp_clear(&vd);
|
||||
vorbis_analysis_init(&vd, &vi);
|
||||
vorbis_block_init(&vd, &vb);
|
||||
|
||||
Flush();
|
||||
}
|
||||
|
||||
static void
|
||||
copy_tag_to_vorbis_comment(VorbisComment &vc, const Tag &tag)
|
||||
{
|
||||
for (const auto &item : tag) {
|
||||
char name[64];
|
||||
ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
|
||||
vc.AddTag(name, item.value);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VorbisEncoder::SendTag(const Tag &tag)
|
||||
{
|
||||
/* write the vorbis_comment object */
|
||||
|
||||
VorbisComment comment;
|
||||
copy_tag_to_vorbis_comment(comment, tag);
|
||||
|
||||
/* reset ogg_stream_state and begin a new stream */
|
||||
|
||||
stream.Reinitialize(GenerateSerial());
|
||||
|
||||
/* send that vorbis_comment to the ogg_stream_state */
|
||||
|
||||
HeaderOut(comment);
|
||||
}
|
||||
|
||||
static void
|
||||
interleaved_to_vorbis_buffer(float **dest, const float *src,
|
||||
std::size_t num_frames, std::size_t num_channels)
|
||||
{
|
||||
for (unsigned i = 0; i < num_frames; i++)
|
||||
for (unsigned j = 0; j < num_channels; j++)
|
||||
dest[j][i] = *src++;
|
||||
}
|
||||
|
||||
void
|
||||
VorbisEncoder::Write(std::span<const std::byte> src)
|
||||
{
|
||||
std::size_t num_frames = src.size() / audio_format.GetFrameSize();
|
||||
|
||||
/* this is for only 16-bit audio */
|
||||
|
||||
interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&vd, num_frames),
|
||||
(const float *)(const void *)src.data(),
|
||||
num_frames,
|
||||
audio_format.channels);
|
||||
|
||||
vorbis_analysis_wrote(&vd, num_frames);
|
||||
BlockOut();
|
||||
}
|
||||
|
||||
const EncoderPlugin vorbis_encoder_plugin = {
|
||||
"vorbis",
|
||||
vorbis_encoder_init,
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ENCODER_VORBIS_H
|
||||
#define MPD_ENCODER_VORBIS_H
|
||||
|
||||
extern const struct EncoderPlugin vorbis_encoder_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,217 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||
|
||||
#include "WaveEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "tag/RiffFormat.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
class WaveEncoder final : public Encoder {
|
||||
unsigned bits;
|
||||
|
||||
DynamicFifoBuffer<std::byte> buffer{8192};
|
||||
|
||||
public:
|
||||
explicit WaveEncoder(AudioFormat &audio_format) noexcept;
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
void Write(std::span<const std::byte> src) override;
|
||||
|
||||
std::span<const std::byte> Read(std::span<std::byte> b) noexcept override {
|
||||
return b.first(buffer.Read(b.data(), b.size()));
|
||||
}
|
||||
};
|
||||
|
||||
class PreparedWaveEncoder final : public PreparedEncoder {
|
||||
/* virtual methods from class PreparedEncoder */
|
||||
Encoder *Open(AudioFormat &audio_format) override {
|
||||
return new WaveEncoder(audio_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||
return "audio/wav";
|
||||
}
|
||||
};
|
||||
|
||||
struct WaveHeader {
|
||||
RiffFileHeader file_header;
|
||||
RiffChunkHeader fmt_header;
|
||||
RiffFmtChunk fmt;
|
||||
RiffChunkHeader data_header;
|
||||
};
|
||||
|
||||
static_assert(sizeof(WaveHeader) == 44);
|
||||
|
||||
static WaveHeader
|
||||
MakeWaveHeader(int channels, int bits,
|
||||
int freq, int block_size) noexcept
|
||||
{
|
||||
WaveHeader header{};
|
||||
|
||||
int data_size = 0x0FFFFFFF;
|
||||
|
||||
/* constants */
|
||||
memcpy(header.file_header.id, "RIFF", 4);
|
||||
memcpy(header.file_header.format, "WAVE", 4);
|
||||
memcpy(header.fmt_header.id, "fmt ", 4);
|
||||
memcpy(header.data_header.id, "data", 4);
|
||||
|
||||
/* wave format */
|
||||
header.fmt.tag = ToLE16(RiffFmtChunk::TAG_PCM);
|
||||
header.fmt.channels = ToLE16(channels);
|
||||
header.fmt.bits_per_sample = ToLE16(bits);
|
||||
header.fmt.sample_rate = ToLE32(freq);
|
||||
header.fmt.block_align = ToLE16(block_size);
|
||||
header.fmt.byte_rate = ToLE32(freq * block_size);
|
||||
|
||||
/* chunk sizes (fake data length) */
|
||||
header.fmt_header.size = ToLE32(sizeof(header.fmt));
|
||||
header.data_header.size = ToLE32(data_size);
|
||||
header.file_header.size = ToLE32(4 +
|
||||
sizeof(header.fmt_header) + sizeof(header.fmt) +
|
||||
sizeof(header.data_header) + data_size);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
static PreparedEncoder *
|
||||
wave_encoder_init([[maybe_unused]] const ConfigBlock &block)
|
||||
{
|
||||
return new PreparedWaveEncoder();
|
||||
}
|
||||
|
||||
WaveEncoder::WaveEncoder(AudioFormat &audio_format) noexcept
|
||||
:Encoder(false)
|
||||
{
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S8:
|
||||
bits = 8;
|
||||
break;
|
||||
|
||||
case SampleFormat::S16:
|
||||
bits = 16;
|
||||
break;
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
bits = 24;
|
||||
break;
|
||||
|
||||
case SampleFormat::S32:
|
||||
bits = 32;
|
||||
break;
|
||||
|
||||
default:
|
||||
audio_format.format = SampleFormat::S16;
|
||||
bits = 16;
|
||||
break;
|
||||
}
|
||||
|
||||
auto range = buffer.Write();
|
||||
assert(range.size() >= sizeof(WaveHeader));
|
||||
auto *header = (WaveHeader *)(void *)range.data();
|
||||
|
||||
/* create PCM wave header in initial buffer */
|
||||
*header = MakeWaveHeader(audio_format.channels,
|
||||
bits,
|
||||
audio_format.sample_rate,
|
||||
(bits / 8) * audio_format.channels);
|
||||
|
||||
buffer.Append(sizeof(*header));
|
||||
}
|
||||
|
||||
static size_t
|
||||
pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
|
||||
{
|
||||
size_t cnt = length >> 1;
|
||||
while (cnt > 0) {
|
||||
*dst16++ = ToLE16(*src16++);
|
||||
cnt--;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t
|
||||
pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) noexcept
|
||||
{
|
||||
size_t cnt = length >> 2;
|
||||
while (cnt > 0){
|
||||
*dst32++ = ToLE32(*src32++);
|
||||
cnt--;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t
|
||||
pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) noexcept
|
||||
{
|
||||
uint32_t value;
|
||||
uint8_t *dst_old = dst8;
|
||||
|
||||
length = length >> 2;
|
||||
while (length > 0){
|
||||
value = *src32++;
|
||||
*dst8++ = (value) & 0xFF;
|
||||
*dst8++ = (value >> 8) & 0xFF;
|
||||
*dst8++ = (value >> 16) & 0xFF;
|
||||
length--;
|
||||
}
|
||||
//correct buffer length
|
||||
return (dst8 - dst_old);
|
||||
}
|
||||
|
||||
void
|
||||
WaveEncoder::Write(std::span<const std::byte> src)
|
||||
{
|
||||
std::size_t length = src.size();
|
||||
std::byte *dst = buffer.Write(length);
|
||||
|
||||
if (IsLittleEndian()) {
|
||||
switch (bits) {
|
||||
case 8:
|
||||
case 16:
|
||||
case 32:// optimized cases
|
||||
memcpy(dst, src.data(), length);
|
||||
break;
|
||||
case 24:
|
||||
length = pcm24_to_wave((uint8_t *)dst,
|
||||
(const uint32_t *)(const void *)src.data(),
|
||||
length);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (bits) {
|
||||
case 8:
|
||||
memcpy(dst, src.data(), length);
|
||||
break;
|
||||
case 16:
|
||||
length = pcm16_to_wave((uint16_t *)dst,
|
||||
(const uint16_t *)(const void *)src.data(),
|
||||
length);
|
||||
break;
|
||||
case 24:
|
||||
length = pcm24_to_wave((uint8_t *)dst,
|
||||
(const uint32_t *)(const void *)src.data(),
|
||||
length);
|
||||
break;
|
||||
case 32:
|
||||
length = pcm32_to_wave((uint32_t *)dst,
|
||||
(const uint32_t *)(const void *)src.data(),
|
||||
length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.Append(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub implementation - encoder support not needed for database creation
|
||||
*/
|
||||
const EncoderPlugin wave_encoder_plugin = {
|
||||
"wave",
|
||||
wave_encoder_init,
|
||||
};
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Encoder support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_ENCODER_WAVE_HXX
|
||||
#define MPD_ENCODER_WAVE_HXX
|
||||
#ifndef MPD_WAVE_ENCODER_PLUGIN_HXX
|
||||
#define MPD_WAVE_ENCODER_PLUGIN_HXX
|
||||
|
||||
extern const struct EncoderPlugin wave_encoder_plugin;
|
||||
#include "encoder/EncoderPlugin.hxx"
|
||||
|
||||
/**
|
||||
* Stub implementation - encoder support not needed for database creation
|
||||
*/
|
||||
extern const EncoderPlugin wave_encoder_plugin;
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
encoder_plugins_sources = [
|
||||
'NullEncoderPlugin.cxx',
|
||||
]
|
||||
|
||||
encoder_features.set('ENABLE_FLAC_ENCODER', flac_dep.found())
|
||||
if flac_dep.found()
|
||||
encoder_plugins_sources += 'FlacEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
if libopus_dep.found()
|
||||
encoder_plugins_sources += 'OpusEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
encoder_features.set('ENABLE_VORBISENC', libvorbisenc_dep.found())
|
||||
if libvorbisenc_dep.found()
|
||||
encoder_plugins_sources += 'VorbisEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
if not get_option('lame').disabled()
|
||||
# LAME doesn't have a pkg-config file so we have to use
|
||||
# find_library()
|
||||
liblame_dep = c_compiler.find_library('mp3lame', required: false)
|
||||
if not liblame_dep.found()
|
||||
# only if that was not found, use dependency() which may use the
|
||||
# LAME subproject
|
||||
liblame_dep = dependency('mp3lame', required: get_option('lame'))
|
||||
endif
|
||||
else
|
||||
liblame_dep = dependency('', required: false)
|
||||
endif
|
||||
|
||||
encoder_features.set('ENABLE_LAME', liblame_dep.found())
|
||||
if liblame_dep.found()
|
||||
encoder_plugins_sources += 'LameEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
libtwolame_dep = dependency('twolame', required: get_option('twolame'))
|
||||
encoder_features.set('ENABLE_TWOLAME', libtwolame_dep.found())
|
||||
if libtwolame_dep.found()
|
||||
encoder_plugins_sources += 'TwolameEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
libshine_dep = dependency('shine', version: '>= 3.1', required: get_option('shine'))
|
||||
encoder_features.set('ENABLE_SHINE', libshine_dep.found())
|
||||
if libshine_dep.found()
|
||||
encoder_plugins_sources += 'ShineEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
|
||||
if get_option('wave_encoder') or need_wave_encoder
|
||||
encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
encoder_plugins = static_library(
|
||||
'encoder_plugins',
|
||||
encoder_plugins_sources,
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
pcm_basic_dep,
|
||||
flac_dep,
|
||||
ogg_dep,
|
||||
libopus_dep,
|
||||
libvorbisenc_dep,
|
||||
libvorbis_dep,
|
||||
liblame_dep,
|
||||
libtwolame_dep,
|
||||
libshine_dep,
|
||||
log_dep,
|
||||
],
|
||||
)
|
||||
|
||||
encoder_plugins_dep = declare_dependency(
|
||||
link_with: encoder_plugins,
|
||||
dependencies: [
|
||||
encoder_api_dep,
|
||||
tag_dep,
|
||||
pcm_dep,
|
||||
config_dep,
|
||||
],
|
||||
)
|
||||
@ -1,23 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "Factory.hxx"
|
||||
#include "LoadOne.hxx"
|
||||
#include "Prepared.hxx"
|
||||
#include "config/Data.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
FilterFactory::MakeFilter(const char *name)
|
||||
{
|
||||
const auto *cfg = config.FindBlock(ConfigBlockOption::AUDIO_FILTER,
|
||||
"name", name);
|
||||
if (cfg == nullptr)
|
||||
throw FmtRuntimeError("Filter template not found: {}",
|
||||
name);
|
||||
|
||||
cfg->SetUsed();
|
||||
|
||||
return filter_configured_new(*cfg);
|
||||
}
|
||||
@ -1,22 +1,18 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_FILTER_FACTORY_HXX
|
||||
#define MPD_FILTER_FACTORY_HXX
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct ConfigData;
|
||||
class PreparedFilter;
|
||||
|
||||
/**
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
class FilterFactory {
|
||||
const ConfigData &config;
|
||||
|
||||
public:
|
||||
explicit FilterFactory(const ConfigData &_config) noexcept
|
||||
:config(_config) {}
|
||||
|
||||
std::unique_ptr<PreparedFilter> MakeFilter(const char *name);
|
||||
FilterFactory([[maybe_unused]] const ConfigData &config) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#include "Filter.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
|
||||
const AudioFormat &
|
||||
Filter::GetOutAudioFormat() const noexcept
|
||||
{
|
||||
// Return a static dummy audio format with minimal valid initialization
|
||||
static const AudioFormat dummy_format{44100, SampleFormat::S16, 2};
|
||||
return dummy_format;
|
||||
}
|
||||
@ -1,84 +1,42 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_FILTER_HXX
|
||||
#define MPD_FILTER_HXX
|
||||
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <span>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
class Filter {
|
||||
protected:
|
||||
AudioFormat out_audio_format;
|
||||
|
||||
explicit Filter(AudioFormat _out_audio_format) noexcept
|
||||
:out_audio_format(_out_audio_format) {
|
||||
assert(out_audio_format.IsValid());
|
||||
}
|
||||
struct AudioFormat;
|
||||
|
||||
/**
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
class Filter {
|
||||
public:
|
||||
virtual ~Filter() noexcept = default;
|
||||
virtual ~Filter() = default;
|
||||
|
||||
/**
|
||||
* Returns the #AudioFormat produced by FilterPCM().
|
||||
*/
|
||||
const AudioFormat &GetOutAudioFormat() const noexcept {
|
||||
return out_audio_format;
|
||||
virtual std::span<const std::byte> FilterPCM(std::span<const std::byte> src) {
|
||||
return src; // Pass-through stub
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the filter's state, e.g. drop/flush buffers.
|
||||
*/
|
||||
virtual void Reset() noexcept {
|
||||
virtual std::span<const std::byte> Flush() {
|
||||
return {}; // Empty stub
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a block of PCM data.
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param src the input buffer
|
||||
* @return the output buffer (will be invalidated by deleting
|
||||
* this object or any call to Reset(), FilterPCM(), ReadMore()
|
||||
* or Flush()); may be empty if no output is currently
|
||||
* available
|
||||
*/
|
||||
virtual std::span<const std::byte> FilterPCM(std::span<const std::byte> src) = 0;
|
||||
|
||||
/**
|
||||
* Read more result data from the filter. After each
|
||||
* FilterPCM() call, this should be called repeatedly until it
|
||||
* returns an empty span.
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @return the output buffer (will be invalidated by deleting
|
||||
* this object or any call to Reset(), FilterPCM(), ReadMore()
|
||||
* or Flush()); may be empty if no output is currently
|
||||
* available
|
||||
*/
|
||||
virtual std::span<const std::byte> ReadMore() {
|
||||
return {};
|
||||
return {}; // Empty stub
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush pending data and return it. This should be called
|
||||
* repeatedly until it returns an empty span.
|
||||
*
|
||||
* After calling this method, this object cannot be used again
|
||||
* (not even Reset() is allowed).
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @return pending data (will be invalidated by deleting this
|
||||
* object or by any call to Flush())
|
||||
*/
|
||||
virtual std::span<const std::byte> Flush() {
|
||||
return {};
|
||||
virtual void Reset() noexcept {
|
||||
// No-op stub
|
||||
}
|
||||
|
||||
virtual const AudioFormat &GetOutAudioFormat() const noexcept;
|
||||
};
|
||||
|
||||
class PreparedFilter; // Forward declaration - see Prepared.hxx for full definition
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
/** \file
|
||||
*
|
||||
* This header declares the filter_plugin class. It describes a
|
||||
* plugin API for objects which filter raw PCM data.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FILTER_PLUGIN_HXX
|
||||
#define MPD_FILTER_PLUGIN_HXX
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct ConfigBlock;
|
||||
class PreparedFilter;
|
||||
|
||||
struct FilterPlugin {
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* Allocates and configures a filter.
|
||||
*/
|
||||
std::unique_ptr<PreparedFilter> (*init)(const ConfigBlock &block);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,40 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "LoadChain.hxx"
|
||||
#include "Factory.hxx"
|
||||
#include "Prepared.hxx"
|
||||
#include "plugins/AutoConvertFilterPlugin.hxx"
|
||||
#include "plugins/TwoFilters.hxx"
|
||||
#include "util/IterableSplitString.hxx"
|
||||
|
||||
#include <string>
|
||||
|
||||
static void
|
||||
filter_chain_append_new(std::unique_ptr<PreparedFilter> &chain,
|
||||
FilterFactory &factory,
|
||||
std::string_view template_name)
|
||||
{
|
||||
/* using the AutoConvert filter just in case the specified
|
||||
filter plugin does not support the exact input format */
|
||||
|
||||
chain = ChainFilters(std::move(chain),
|
||||
/* unfortunately, MakeFilter() wants a
|
||||
null-terminated string, so we need to
|
||||
copy it here */
|
||||
autoconvert_filter_new(factory.MakeFilter(std::string(template_name).c_str())),
|
||||
template_name);
|
||||
}
|
||||
|
||||
void
|
||||
filter_chain_parse(std::unique_ptr<PreparedFilter> &chain,
|
||||
FilterFactory &factory,
|
||||
const char *spec)
|
||||
{
|
||||
for (const std::string_view i : IterableSplitString(spec, ',')) {
|
||||
if (i.empty())
|
||||
continue;
|
||||
|
||||
filter_chain_append_new(chain, factory, i);
|
||||
}
|
||||
}
|
||||
@ -1,28 +1,31 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_FILTER_LOAD_CHAIN_HXX
|
||||
#define MPD_FILTER_LOAD_CHAIN_HXX
|
||||
|
||||
#include "Prepared.hxx"
|
||||
#include <memory>
|
||||
|
||||
class FilterFactory;
|
||||
class PreparedFilter;
|
||||
struct ConfigBlock;
|
||||
|
||||
/**
|
||||
* Builds a filter chain from a configuration string on the form
|
||||
* "name1, name2, name3, ..." by looking up each name among the
|
||||
* configured filter sections.
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param chain the chain to append filters on
|
||||
* @param config the global configuration to load filter definitions from
|
||||
* @param spec the filter chain specification
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
void
|
||||
filter_chain_parse(std::unique_ptr<PreparedFilter> &chain,
|
||||
FilterFactory &factory,
|
||||
const char *spec);
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
filter_chain_parse([[maybe_unused]] std::unique_ptr<PreparedFilter> &prepared,
|
||||
[[maybe_unused]] FilterFactory &factory,
|
||||
[[maybe_unused]] const char *spec)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
filter_chain_new([[maybe_unused]] const ConfigBlock *block)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "LoadOne.hxx"
|
||||
#include "FilterPlugin.hxx"
|
||||
#include "Registry.hxx"
|
||||
#include "Prepared.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
filter_configured_new(const ConfigBlock &block)
|
||||
{
|
||||
const char *plugin_name = block.GetBlockValue("plugin");
|
||||
if (plugin_name == nullptr)
|
||||
throw std::runtime_error("No filter plugin specified");
|
||||
|
||||
const auto *plugin = filter_plugin_by_name(plugin_name);
|
||||
if (plugin == nullptr)
|
||||
throw FmtRuntimeError("No such filter plugin: {}",
|
||||
plugin_name);
|
||||
|
||||
return plugin->init(block);
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_FILTER_LOAD_ONE_HXX
|
||||
#define MPD_FILTER_LOAD_ONE_HXX
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct ConfigBlock;
|
||||
class PreparedFilter;
|
||||
|
||||
/**
|
||||
* Creates a new filter, loads configuration and the plugin name from
|
||||
* the specified configuration section.
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param block the configuration section
|
||||
*/
|
||||
std::unique_ptr<PreparedFilter>
|
||||
filter_configured_new(const ConfigBlock &block);
|
||||
|
||||
#endif
|
||||
@ -1,18 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_NULL_FILTER_HXX
|
||||
#define MPD_NULL_FILTER_HXX
|
||||
|
||||
#include "filter/Filter.hxx"
|
||||
|
||||
class NullFilter final : public Filter {
|
||||
public:
|
||||
explicit NullFilter(const AudioFormat &af):Filter(af) {}
|
||||
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override {
|
||||
return src;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,114 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "Observer.hxx"
|
||||
#include "Filter.hxx"
|
||||
#include "Prepared.hxx"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
class FilterObserver::PreparedProxy final : public PreparedFilter {
|
||||
FilterObserver &observer;
|
||||
|
||||
std::unique_ptr<PreparedFilter> prepared_filter;
|
||||
Proxy *child = nullptr;
|
||||
|
||||
public:
|
||||
PreparedProxy(FilterObserver &_observer,
|
||||
std::unique_ptr<PreparedFilter> _prepared_filter) noexcept
|
||||
:observer(_observer),
|
||||
prepared_filter(std::move(_prepared_filter)) {}
|
||||
|
||||
~PreparedProxy() noexcept override {
|
||||
assert(child == nullptr);
|
||||
assert(observer.proxy == this);
|
||||
|
||||
observer.proxy = nullptr;
|
||||
}
|
||||
|
||||
PreparedProxy(const PreparedProxy &) = delete;
|
||||
PreparedProxy &operator=(const PreparedProxy &) = delete;
|
||||
|
||||
void Clear([[maybe_unused]] Proxy *_child) noexcept {
|
||||
assert(child == _child);
|
||||
child = nullptr;
|
||||
}
|
||||
|
||||
Filter *Get() noexcept;
|
||||
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
class FilterObserver::Proxy final : public Filter {
|
||||
PreparedProxy &parent;
|
||||
|
||||
std::unique_ptr<Filter> filter;
|
||||
|
||||
public:
|
||||
Proxy(PreparedProxy &_parent, std::unique_ptr<Filter> _filter) noexcept
|
||||
:Filter(_filter->GetOutAudioFormat()),
|
||||
parent(_parent), filter(std::move(_filter)) {}
|
||||
|
||||
~Proxy() noexcept override {
|
||||
parent.Clear(this);
|
||||
}
|
||||
|
||||
Proxy(const Proxy &) = delete;
|
||||
Proxy &operator=(const Proxy &) = delete;
|
||||
|
||||
Filter *Get() noexcept {
|
||||
return filter.get();
|
||||
}
|
||||
|
||||
void Reset() noexcept override {
|
||||
filter->Reset();
|
||||
}
|
||||
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override {
|
||||
return filter->FilterPCM(src);
|
||||
}
|
||||
|
||||
std::span<const std::byte> ReadMore() override {
|
||||
return filter->ReadMore();
|
||||
}
|
||||
|
||||
std::span<const std::byte> Flush() override {
|
||||
return filter->Flush();
|
||||
}
|
||||
};
|
||||
|
||||
Filter *
|
||||
FilterObserver::PreparedProxy::Get() noexcept
|
||||
{
|
||||
return child != nullptr
|
||||
? child->Get()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
FilterObserver::PreparedProxy::Open(AudioFormat &af)
|
||||
{
|
||||
assert(child == nullptr);
|
||||
|
||||
auto c = std::make_unique<Proxy>(*this, prepared_filter->Open(af));
|
||||
child = c.get();
|
||||
return c;
|
||||
}
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
FilterObserver::Set(std::unique_ptr<PreparedFilter> pf)
|
||||
{
|
||||
assert(proxy == nullptr);
|
||||
|
||||
auto p = std::make_unique<PreparedProxy>(*this, std::move(pf));
|
||||
proxy = p.get();
|
||||
return p;
|
||||
}
|
||||
|
||||
Filter *
|
||||
FilterObserver::Get() noexcept
|
||||
{
|
||||
return proxy != nullptr
|
||||
? proxy->Get()
|
||||
: nullptr;
|
||||
}
|
||||
@ -1,31 +1,32 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_FILTER_OBSERVER_HXX
|
||||
#define MPD_FILTER_OBSERVER_HXX
|
||||
|
||||
#include <memory>
|
||||
|
||||
class PreparedFilter;
|
||||
class Filter;
|
||||
class PreparedFilter;
|
||||
|
||||
/**
|
||||
* A helper class which observes calls to a #PreparedFilter and allows
|
||||
* the caller to access the #Filter instances created by it.
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
class FilterObserver {
|
||||
class PreparedProxy;
|
||||
class Proxy;
|
||||
public:
|
||||
FilterObserver() noexcept = default;
|
||||
|
||||
PreparedProxy *proxy = nullptr;
|
||||
Filter *Get() noexcept { return nullptr; }
|
||||
|
||||
public:
|
||||
/**
|
||||
* @return a proxy object
|
||||
*/
|
||||
std::unique_ptr<PreparedFilter> Set(std::unique_ptr<PreparedFilter> pf);
|
||||
void Set(Filter *) noexcept {}
|
||||
|
||||
Filter *Get() noexcept;
|
||||
// Also accept PreparedFilter unique_ptr, return it for chaining
|
||||
// Use template to avoid incomplete type issues
|
||||
template<typename T>
|
||||
std::unique_ptr<T> Set(std::unique_ptr<T> p) noexcept {
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,28 +1,25 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_PREPARED_FILTER_HXX
|
||||
#define MPD_PREPARED_FILTER_HXX
|
||||
#ifndef MPD_FILTER_PREPARED_HXX
|
||||
#define MPD_FILTER_PREPARED_HXX
|
||||
|
||||
#include "Filter.hxx"
|
||||
#include <memory>
|
||||
|
||||
struct AudioFormat;
|
||||
class Filter;
|
||||
|
||||
/**
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
class PreparedFilter {
|
||||
public:
|
||||
virtual ~PreparedFilter() = default;
|
||||
|
||||
/**
|
||||
* Opens the filter, preparing it for FilterPCM().
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param af the audio format of incoming data; the
|
||||
* plugin may modify the object to enforce another input
|
||||
* format
|
||||
*/
|
||||
virtual std::unique_ptr<Filter> Open(AudioFormat &af) = 0;
|
||||
virtual std::unique_ptr<Filter> Open([[maybe_unused]] const AudioFormat &af) {
|
||||
return nullptr; // Stub returns nullptr
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "Registry.hxx"
|
||||
#include "FilterPlugin.hxx"
|
||||
#include "plugins/NullFilterPlugin.hxx"
|
||||
#include "plugins/RouteFilterPlugin.hxx"
|
||||
#include "plugins/NormalizeFilterPlugin.hxx"
|
||||
#include "plugins/FfmpegFilterPlugin.hxx"
|
||||
#include "plugins/HdcdFilterPlugin.hxx"
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static constinit const FilterPlugin *const filter_plugins[] = {
|
||||
&null_filter_plugin,
|
||||
&route_filter_plugin,
|
||||
&normalize_filter_plugin,
|
||||
#ifdef HAVE_LIBAVFILTER
|
||||
&ffmpeg_filter_plugin,
|
||||
&hdcd_filter_plugin,
|
||||
#endif
|
||||
nullptr,
|
||||
};
|
||||
|
||||
const FilterPlugin *
|
||||
filter_plugin_by_name(const char *name) noexcept
|
||||
{
|
||||
for (unsigned i = 0; filter_plugins[i] != nullptr; ++i)
|
||||
if (strcmp(filter_plugins[i]->name, name) == 0)
|
||||
return filter_plugins[i];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
/** \file
|
||||
*
|
||||
* This library manages all filter plugins which are enabled at
|
||||
* compile time.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FILTER_REGISTRY_HXX
|
||||
#define MPD_FILTER_REGISTRY_HXX
|
||||
|
||||
struct FilterPlugin;
|
||||
|
||||
[[gnu::pure]]
|
||||
const FilterPlugin *
|
||||
filter_plugin_by_name(const char *name) noexcept;
|
||||
|
||||
#endif
|
||||
@ -1,56 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "AutoConvertFilterPlugin.hxx"
|
||||
#include "ConvertFilterPlugin.hxx"
|
||||
#include "TwoFilters.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
class PreparedAutoConvertFilter final : public PreparedFilter {
|
||||
/**
|
||||
* The underlying filter.
|
||||
*/
|
||||
std::unique_ptr<PreparedFilter> filter;
|
||||
|
||||
public:
|
||||
explicit PreparedAutoConvertFilter(std::unique_ptr<PreparedFilter> _filter) noexcept
|
||||
:filter(std::move(_filter)) {}
|
||||
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format)
|
||||
{
|
||||
assert(in_audio_format.IsValid());
|
||||
|
||||
/* open the "real" filter */
|
||||
|
||||
AudioFormat child_audio_format = in_audio_format;
|
||||
auto new_filter = filter->Open(child_audio_format);
|
||||
|
||||
/* need to convert? */
|
||||
|
||||
if (in_audio_format == child_audio_format)
|
||||
/* no */
|
||||
return new_filter;
|
||||
|
||||
/* yes - create a convert_filter */
|
||||
|
||||
auto convert = convert_filter_new(in_audio_format,
|
||||
child_audio_format);
|
||||
|
||||
return std::make_unique<TwoFilters>(std::move(convert),
|
||||
std::move(new_filter));
|
||||
}
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept
|
||||
{
|
||||
return std::make_unique<PreparedAutoConvertFilter>(std::move(filter));
|
||||
}
|
||||
@ -1,20 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
||||
#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
||||
#ifndef MPD_AUTO_CONVERT_FILTER_PLUGIN_HXX
|
||||
#define MPD_AUTO_CONVERT_FILTER_PLUGIN_HXX
|
||||
|
||||
#include "filter/Prepared.hxx"
|
||||
#include <memory>
|
||||
|
||||
class PreparedFilter;
|
||||
|
||||
/**
|
||||
* Creates a new "autoconvert" filter. When opened, it ensures that
|
||||
* the input audio format isn't changed. If the underlying filter
|
||||
* requests a different format, it automatically creates a
|
||||
* convert_filter.
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
std::unique_ptr<PreparedFilter>
|
||||
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept;
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
autoconvert_filter_new([[maybe_unused]] std::unique_ptr<PreparedFilter> a = nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,118 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "ConvertFilterPlugin.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Convert.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
class ConvertFilter final : public Filter {
|
||||
/**
|
||||
* The input audio format; PCM data is passed to the filter()
|
||||
* method in this format.
|
||||
*/
|
||||
const AudioFormat in_audio_format;
|
||||
|
||||
/**
|
||||
* This object is only "open" if #in_audio_format !=
|
||||
* #out_audio_format.
|
||||
*/
|
||||
std::unique_ptr<PcmConvert> state;
|
||||
|
||||
public:
|
||||
explicit ConvertFilter(const AudioFormat &audio_format);
|
||||
|
||||
void Set(const AudioFormat &_out_audio_format);
|
||||
|
||||
void Reset() noexcept override {
|
||||
if (state)
|
||||
state->Reset();
|
||||
}
|
||||
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
|
||||
|
||||
std::span<const std::byte> Flush() override {
|
||||
return state
|
||||
? state->Flush()
|
||||
: std::span<const std::byte>{};
|
||||
}
|
||||
};
|
||||
|
||||
class PreparedConvertFilter final : public PreparedFilter {
|
||||
public:
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
void
|
||||
ConvertFilter::Set(const AudioFormat &_out_audio_format)
|
||||
{
|
||||
assert(_out_audio_format.IsValid());
|
||||
|
||||
if (_out_audio_format == out_audio_format)
|
||||
/* no change */
|
||||
return;
|
||||
|
||||
if (state) {
|
||||
out_audio_format = in_audio_format;
|
||||
state.reset();
|
||||
}
|
||||
|
||||
if (_out_audio_format == in_audio_format)
|
||||
/* optimized special case: no-op */
|
||||
return;
|
||||
|
||||
state = std::make_unique<PcmConvert>(in_audio_format,
|
||||
_out_audio_format);
|
||||
|
||||
out_audio_format = _out_audio_format;
|
||||
}
|
||||
|
||||
ConvertFilter::ConvertFilter(const AudioFormat &audio_format)
|
||||
:Filter(audio_format), in_audio_format(audio_format)
|
||||
{
|
||||
assert(in_audio_format.IsValid());
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedConvertFilter::Open(AudioFormat &audio_format)
|
||||
{
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
return std::make_unique<ConvertFilter>(audio_format);
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
ConvertFilter::FilterPCM(std::span<const std::byte> src)
|
||||
{
|
||||
return state
|
||||
? state->Convert(src)
|
||||
/* optimized special case: no-op */
|
||||
: std::span<const std::byte>{src};
|
||||
}
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
convert_filter_prepare() noexcept
|
||||
{
|
||||
return std::make_unique<PreparedConvertFilter>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
convert_filter_new(const AudioFormat in_audio_format,
|
||||
const AudioFormat out_audio_format)
|
||||
{
|
||||
auto filter = std::make_unique<ConvertFilter>(in_audio_format);
|
||||
filter->Set(out_audio_format);
|
||||
return filter;
|
||||
}
|
||||
|
||||
void
|
||||
convert_filter_set(Filter *_filter, AudioFormat out_audio_format)
|
||||
{
|
||||
auto *filter = (ConvertFilter *)_filter;
|
||||
|
||||
filter->Set(out_audio_format);
|
||||
}
|
||||
@ -1,31 +1,37 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX
|
||||
#define MPD_CONVERT_FILTER_PLUGIN_HXX
|
||||
|
||||
#include "filter/Prepared.hxx"
|
||||
#include <memory>
|
||||
|
||||
class PreparedFilter;
|
||||
class Filter;
|
||||
struct AudioFormat;
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
convert_filter_prepare() noexcept;
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
convert_filter_new(AudioFormat in_audio_format,
|
||||
AudioFormat out_audio_format);
|
||||
class Filter;
|
||||
|
||||
/**
|
||||
* Sets the output audio format for the specified filter. You must
|
||||
* call this after the filter has been opened. Since this audio
|
||||
* format switch is a violation of the filter API, this filter must be
|
||||
* the last in a chain.
|
||||
*
|
||||
* Throws on error.
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
void
|
||||
convert_filter_set(Filter *filter, AudioFormat out_audio_format);
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
convert_filter_new([[maybe_unused]] const AudioFormat &in,
|
||||
[[maybe_unused]] const AudioFormat &out)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void
|
||||
convert_filter_set([[maybe_unused]] Filter *,
|
||||
[[maybe_unused]] const AudioFormat &) noexcept
|
||||
{
|
||||
// No-op stub
|
||||
}
|
||||
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
convert_filter_prepare()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "FfmpegFilter.hxx"
|
||||
#include "lib/ffmpeg/Interleave.hxx"
|
||||
#include "lib/ffmpeg/SampleFormat.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
}
|
||||
|
||||
#include <string.h>
|
||||
|
||||
FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
|
||||
const AudioFormat &_out_audio_format,
|
||||
Ffmpeg::FilterGraph &&_graph,
|
||||
AVFilterContext &_buffer_src,
|
||||
AVFilterContext &_buffer_sink) noexcept
|
||||
:Filter(_out_audio_format),
|
||||
graph(std::move(_graph)),
|
||||
buffer_src(_buffer_src),
|
||||
buffer_sink(_buffer_sink),
|
||||
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
|
||||
in_sample_rate(in_audio_format.sample_rate),
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
|
||||
in_channels(in_audio_format.channels),
|
||||
#endif
|
||||
in_audio_frame_size(in_audio_format.GetFrameSize()),
|
||||
out_audio_frame_size(_out_audio_format.GetFrameSize())
|
||||
{
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::span<const std::byte>
|
||||
FfmpegFilter::ReadOutput()
|
||||
{
|
||||
frame.Unref();
|
||||
|
||||
if (int err = av_buffersink_get_frame(&buffer_sink, frame.get()); err < 0) {
|
||||
if (err == AVERROR(EAGAIN) || err == AVERROR_EOF)
|
||||
return {};
|
||||
|
||||
throw MakeFfmpegError(err, "av_buffersink_get_frame() failed");
|
||||
}
|
||||
|
||||
return Ffmpeg::InterleaveFrame(*frame, interleave_buffer);
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
FfmpegFilter::FilterPCM(std::span<const std::byte> src)
|
||||
{
|
||||
assert(!flushed);
|
||||
|
||||
/* submit source data into the FFmpeg audio buffer source */
|
||||
|
||||
frame.Unref();
|
||||
frame->format = in_format;
|
||||
frame->sample_rate = in_sample_rate;
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
frame->ch_layout = in_ch_layout;
|
||||
#else
|
||||
frame->channels = in_channels;
|
||||
#endif
|
||||
frame->nb_samples = src.size() / in_audio_frame_size;
|
||||
|
||||
frame->pts = pts;
|
||||
pts += frame->nb_samples;
|
||||
|
||||
frame.GetBuffer();
|
||||
|
||||
memcpy(frame.GetData(0), src.data(), src.size());
|
||||
|
||||
if (int err = av_buffersrc_add_frame(&buffer_src, frame.get()); err < 0)
|
||||
throw MakeFfmpegError(err, "av_buffersrc_write_frame() failed");
|
||||
|
||||
/* collect filtered data from the FFmpeg audio buffer sink */
|
||||
|
||||
/* TODO: call av_buffersink_get_frame() repeatedly? Not
|
||||
possible with MPD's current Filter API */
|
||||
|
||||
return ReadOutput();
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
FfmpegFilter::ReadMore()
|
||||
{
|
||||
return ReadOutput();
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
FfmpegFilter::Flush()
|
||||
{
|
||||
if (!flushed) {
|
||||
if (int err = av_buffersrc_add_frame(&buffer_src, nullptr); err < 0)
|
||||
throw MakeFfmpegError(err, "av_buffersrc_write_frame() failed");
|
||||
|
||||
flushed = true;
|
||||
}
|
||||
|
||||
return ReadOutput();
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "filter/Filter.hxx"
|
||||
#include "lib/ffmpeg/Buffer.hxx"
|
||||
#include "lib/ffmpeg/Filter.hxx"
|
||||
#include "lib/ffmpeg/Frame.hxx"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* A #Filter implementation using FFmpeg's libavfilter.
|
||||
*/
|
||||
class FfmpegFilter final : public Filter {
|
||||
Ffmpeg::FilterGraph graph;
|
||||
AVFilterContext &buffer_src, &buffer_sink;
|
||||
Ffmpeg::Frame frame;
|
||||
|
||||
FfmpegBuffer interleave_buffer;
|
||||
|
||||
const int in_format, in_sample_rate;
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
AVChannelLayout in_ch_layout;
|
||||
#else
|
||||
const int in_channels;
|
||||
#endif
|
||||
|
||||
const size_t in_audio_frame_size;
|
||||
const size_t out_audio_frame_size;
|
||||
|
||||
/**
|
||||
* Presentation timestamp. A counter for `AVFrame::pts`.
|
||||
*/
|
||||
int_least64_t pts = 0;
|
||||
|
||||
bool flushed = false;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param _graph a checked and configured AVFilterGraph
|
||||
* @param _buffer_src an "abuffer" filter which serves as
|
||||
* input
|
||||
* @param _buffer_sink an "abuffersink" filter which serves as
|
||||
* output
|
||||
*/
|
||||
FfmpegFilter(const AudioFormat &in_audio_format,
|
||||
const AudioFormat &_out_audio_format,
|
||||
Ffmpeg::FilterGraph &&_graph,
|
||||
AVFilterContext &_buffer_src,
|
||||
AVFilterContext &_buffer_sink) noexcept;
|
||||
|
||||
/* virtual methods from class Filter */
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
|
||||
std::span<const std::byte> ReadMore() override;
|
||||
std::span<const std::byte> Flush() override;
|
||||
|
||||
private:
|
||||
std::span<const std::byte> ReadOutput();
|
||||
};
|
||||
@ -1,114 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "FfmpegFilterPlugin.hxx"
|
||||
#include "FfmpegFilter.hxx"
|
||||
#include "filter/FilterPlugin.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "lib/ffmpeg/Filter.hxx"
|
||||
#include "lib/ffmpeg/DetectFilterFormat.hxx"
|
||||
#include "config/Block.hxx"
|
||||
|
||||
class PreparedFfmpegFilter final : public PreparedFilter {
|
||||
const char *const graph_string;
|
||||
|
||||
public:
|
||||
explicit PreparedFfmpegFilter(const char *_graph) noexcept
|
||||
:graph_string(_graph) {}
|
||||
|
||||
/* virtual methods from class PreparedFilter */
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fallback for PreparedFfmpegFilter::Open() just in case the filter's
|
||||
* native output format could not be determined.
|
||||
*
|
||||
* TODO: improve the MPD filter API to allow returning the output
|
||||
* format later, and eliminate this kludge
|
||||
*/
|
||||
static auto
|
||||
OpenWithAformat(const char *graph_string, AudioFormat &in_audio_format)
|
||||
{
|
||||
Ffmpeg::FilterGraph graph;
|
||||
|
||||
auto &buffer_src =
|
||||
Ffmpeg::MakeAudioBufferSource(in_audio_format, *graph);
|
||||
|
||||
auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph);
|
||||
|
||||
AudioFormat out_audio_format = in_audio_format;
|
||||
auto &aformat = Ffmpeg::MakeAformat(out_audio_format, *graph);
|
||||
|
||||
if (int error = avfilter_link(&aformat, 0, &buffer_sink, 0); error < 0)
|
||||
throw MakeFfmpegError(error, "avfilter_link() failed");
|
||||
|
||||
graph.ParseSingleInOut(graph_string, aformat, buffer_src);
|
||||
graph.CheckAndConfigure();
|
||||
|
||||
return std::make_unique<FfmpegFilter>(in_audio_format,
|
||||
out_audio_format,
|
||||
std::move(graph),
|
||||
buffer_src,
|
||||
buffer_sink);
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedFfmpegFilter::Open(AudioFormat &in_audio_format)
|
||||
{
|
||||
Ffmpeg::FilterGraph graph;
|
||||
|
||||
auto &buffer_src =
|
||||
Ffmpeg::MakeAudioBufferSource(in_audio_format, *graph);
|
||||
|
||||
auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph);
|
||||
|
||||
/* if the filter's output format is not supported by MPD, this
|
||||
"aformat" filter is inserted at the end and takes care for
|
||||
the required conversion */
|
||||
auto &aformat = Ffmpeg::MakeAutoAformat(*graph);
|
||||
|
||||
if (int error = avfilter_link(&aformat, 0, &buffer_sink, 0); error < 0)
|
||||
throw MakeFfmpegError(error, "avfilter_link() failed");
|
||||
|
||||
graph.ParseSingleInOut(graph_string, aformat, buffer_src);
|
||||
graph.CheckAndConfigure();
|
||||
|
||||
const auto out_audio_format =
|
||||
Ffmpeg::DetectFilterOutputFormat(in_audio_format, buffer_src,
|
||||
buffer_sink);
|
||||
|
||||
if (!out_audio_format.IsDefined())
|
||||
/* the filter's native output format could not be
|
||||
determined yet, but we need to know it now; as a
|
||||
workaround for this MPD API deficiency, try again
|
||||
with an "aformat" filter which forces a specific
|
||||
output format */
|
||||
return OpenWithAformat(graph_string, in_audio_format);
|
||||
|
||||
return std::make_unique<FfmpegFilter>(in_audio_format,
|
||||
out_audio_format,
|
||||
std::move(graph),
|
||||
buffer_src,
|
||||
buffer_sink);
|
||||
}
|
||||
|
||||
static std::unique_ptr<PreparedFilter>
|
||||
ffmpeg_filter_init(const ConfigBlock &block)
|
||||
{
|
||||
const char *graph = block.GetBlockValue("graph");
|
||||
if (graph == nullptr)
|
||||
throw std::runtime_error("Missing \"graph\" configuration");
|
||||
|
||||
/* check if the graph can be parsed (and discard the
|
||||
object) */
|
||||
Ffmpeg::FilterGraph().Parse(graph);
|
||||
|
||||
return std::make_unique<PreparedFfmpegFilter>(graph);
|
||||
}
|
||||
|
||||
const FilterPlugin ffmpeg_filter_plugin = {
|
||||
"ffmpeg",
|
||||
ffmpeg_filter_init,
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_FFMPEG_FILTER_PLUGIN_HXX
|
||||
#define MPD_FFMPEG_FILTER_PLUGIN_HXX
|
||||
|
||||
struct FilterPlugin;
|
||||
|
||||
extern const FilterPlugin ffmpeg_filter_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,79 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "HdcdFilterPlugin.hxx"
|
||||
#include "FfmpegFilter.hxx"
|
||||
#include "filter/FilterPlugin.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/NullFilter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "lib/ffmpeg/Filter.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
|
||||
static constexpr const char *hdcd_graph = "hdcd";
|
||||
|
||||
[[gnu::pure]]
|
||||
static bool
|
||||
MaybeHdcd(const AudioFormat &audio_format) noexcept
|
||||
{
|
||||
return audio_format.sample_rate == 44100 &&
|
||||
audio_format.format == SampleFormat::S16 &&
|
||||
audio_format.channels == 2;
|
||||
}
|
||||
|
||||
static auto
|
||||
OpenHdcdFilter(AudioFormat &in_audio_format)
|
||||
{
|
||||
Ffmpeg::FilterGraph graph;
|
||||
|
||||
auto &buffer_src =
|
||||
Ffmpeg::MakeAudioBufferSource(in_audio_format,
|
||||
*graph);
|
||||
|
||||
auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph);
|
||||
|
||||
graph.ParseSingleInOut(hdcd_graph, buffer_sink, buffer_src);
|
||||
graph.CheckAndConfigure();
|
||||
|
||||
auto out_audio_format = in_audio_format;
|
||||
// TODO: convert to 32 bit only if HDCD actually detected
|
||||
out_audio_format.format = SampleFormat::S32;
|
||||
|
||||
return std::make_unique<FfmpegFilter>(in_audio_format,
|
||||
out_audio_format,
|
||||
std::move(graph),
|
||||
buffer_src,
|
||||
buffer_sink);
|
||||
}
|
||||
|
||||
class PreparedHdcdFilter final : public PreparedFilter {
|
||||
public:
|
||||
/* virtual methods from class PreparedFilter */
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedHdcdFilter::Open(AudioFormat &audio_format)
|
||||
{
|
||||
if (MaybeHdcd(audio_format))
|
||||
return OpenHdcdFilter(audio_format);
|
||||
else
|
||||
/* this cannot be HDCD, so let's copy as-is using
|
||||
NullFilter */
|
||||
return std::make_unique<NullFilter>(audio_format);
|
||||
}
|
||||
|
||||
static std::unique_ptr<PreparedFilter>
|
||||
hdcd_filter_init(const ConfigBlock &)
|
||||
{
|
||||
/* check if the graph can be parsed (and discard the
|
||||
object) */
|
||||
Ffmpeg::FilterGraph().Parse(hdcd_graph);
|
||||
|
||||
return std::make_unique<PreparedHdcdFilter>();
|
||||
}
|
||||
|
||||
const FilterPlugin hdcd_filter_plugin = {
|
||||
"hdcd",
|
||||
hdcd_filter_init,
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_HDCD_FILTER_PLUGIN_HXX
|
||||
#define MPD_HDCD_FILTER_PLUGIN_HXX
|
||||
|
||||
struct FilterPlugin;
|
||||
|
||||
extern const FilterPlugin hdcd_filter_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,73 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "NormalizeFilterPlugin.hxx"
|
||||
#include "filter/FilterPlugin.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "pcm/Buffer.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Normalizer.hxx"
|
||||
#include "util/SpanCast.hxx"
|
||||
|
||||
class NormalizeFilter final : public Filter {
|
||||
PcmNormalizer normalizer;
|
||||
|
||||
PcmBuffer buffer;
|
||||
|
||||
public:
|
||||
explicit NormalizeFilter(const AudioFormat &audio_format)
|
||||
:Filter(audio_format) {
|
||||
}
|
||||
|
||||
NormalizeFilter(const NormalizeFilter &) = delete;
|
||||
NormalizeFilter &operator=(const NormalizeFilter &) = delete;
|
||||
|
||||
/* virtual methods from class Filter */
|
||||
void Reset() noexcept override {
|
||||
normalizer.Reset();
|
||||
}
|
||||
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
|
||||
};
|
||||
|
||||
class PreparedNormalizeFilter final : public PreparedFilter {
|
||||
public:
|
||||
/* virtual methods from class PreparedFilter */
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
static std::unique_ptr<PreparedFilter>
|
||||
normalize_filter_init([[maybe_unused]] const ConfigBlock &block)
|
||||
{
|
||||
return std::make_unique<PreparedNormalizeFilter>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedNormalizeFilter::Open(AudioFormat &audio_format)
|
||||
{
|
||||
audio_format.format = SampleFormat::S16;
|
||||
|
||||
return std::make_unique<NormalizeFilter>(audio_format);
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
NormalizeFilter::FilterPCM(std::span<const std::byte> _src)
|
||||
{
|
||||
const auto src = FromBytesStrict<const int16_t>(_src);
|
||||
auto *dest = (int16_t *)buffer.GetT<int16_t>(src.size());
|
||||
|
||||
normalizer.ProcessS16(dest, src);
|
||||
return std::as_bytes(std::span{dest, src.size()});
|
||||
}
|
||||
|
||||
const FilterPlugin normalize_filter_plugin = {
|
||||
"normalize",
|
||||
normalize_filter_init,
|
||||
};
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
normalize_filter_prepare() noexcept
|
||||
{
|
||||
return std::make_unique<PreparedNormalizeFilter>();
|
||||
}
|
||||
@ -1,17 +1,22 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_NORMALIZE_FILTER_PLUGIN_HXX
|
||||
#define MPD_NORMALIZE_FILTER_PLUGIN_HXX
|
||||
|
||||
#include "filter/Prepared.hxx"
|
||||
#include <memory>
|
||||
|
||||
struct FilterPlugin;
|
||||
class PreparedFilter;
|
||||
struct ConfigBlock;
|
||||
|
||||
extern const FilterPlugin normalize_filter_plugin;
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
normalize_filter_prepare() noexcept;
|
||||
/**
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
normalize_filter_prepare([[maybe_unused]] const ConfigBlock *block = nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
/** \file
|
||||
*
|
||||
* This filter plugin does nothing. That is not quite useful, except
|
||||
* for testing the filter core, or as a template for new filter
|
||||
* plugins.
|
||||
*/
|
||||
|
||||
#include "NullFilterPlugin.hxx"
|
||||
#include "filter/FilterPlugin.hxx"
|
||||
#include "filter/NullFilter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
|
||||
class PreparedNullFilter final : public PreparedFilter {
|
||||
public:
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override {
|
||||
return std::make_unique<NullFilter>(af);
|
||||
}
|
||||
};
|
||||
|
||||
static std::unique_ptr<PreparedFilter>
|
||||
null_filter_init([[maybe_unused]] const ConfigBlock &block)
|
||||
{
|
||||
return std::make_unique<PreparedNullFilter>();
|
||||
}
|
||||
|
||||
const FilterPlugin null_filter_plugin = {
|
||||
"null",
|
||||
null_filter_init,
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_NULL_FILTER_PLUGIN_HXX
|
||||
#define MPD_NULL_FILTER_PLUGIN_HXX
|
||||
|
||||
struct FilterPlugin;
|
||||
|
||||
extern const FilterPlugin null_filter_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,217 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "ReplayGainFilterPlugin.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "tag/ReplayGainInfo.hxx"
|
||||
#include "config/ReplayGainConfig.hxx"
|
||||
#include "mixer/Control.hxx"
|
||||
#include "mixer/Mixer.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
|
||||
static constexpr Domain replay_gain_domain("replay_gain");
|
||||
|
||||
class ReplayGainFilter final : public Filter {
|
||||
const ReplayGainConfig config;
|
||||
|
||||
/**
|
||||
* If set, then this hardware mixer is used for applying
|
||||
* replay gain, instead of the software volume library.
|
||||
*/
|
||||
Mixer *const mixer;
|
||||
|
||||
/**
|
||||
* The base volume level for scale=1.0, between 1 and 100
|
||||
* (including).
|
||||
*/
|
||||
const unsigned base;
|
||||
|
||||
ReplayGainMode mode = ReplayGainMode::OFF;
|
||||
|
||||
ReplayGainInfo info;
|
||||
|
||||
/**
|
||||
* About the current volume: it is between 0 and a value that
|
||||
* may or may not exceed #PCM_VOLUME_1.
|
||||
*
|
||||
* If the default value of true is used for replaygain_limit, the
|
||||
* application of the volume to the signal will never cause clipping.
|
||||
*
|
||||
* On the other hand, if the user has set replaygain_limit to false,
|
||||
* the chance of clipping is explicitly preferred if that's required to
|
||||
* maintain a consistent audio level. Whether clipping will actually
|
||||
* occur depends on what value the user is using for replaygain_preamp.
|
||||
*/
|
||||
PcmVolume pv;
|
||||
|
||||
public:
|
||||
ReplayGainFilter(const ReplayGainConfig &_config, bool allow_convert,
|
||||
const AudioFormat &audio_format,
|
||||
Mixer *_mixer, unsigned _base)
|
||||
:Filter(audio_format),
|
||||
config(_config),
|
||||
mixer(_mixer), base(_base) {
|
||||
info.Clear();
|
||||
|
||||
out_audio_format.format = pv.Open(out_audio_format.format,
|
||||
allow_convert);
|
||||
}
|
||||
|
||||
void SetInfo(const ReplayGainInfo *_info) {
|
||||
if (_info != nullptr)
|
||||
info = *_info;
|
||||
else
|
||||
info.Clear();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void SetMode(ReplayGainMode _mode) {
|
||||
if (_mode == mode)
|
||||
/* no change */
|
||||
return;
|
||||
|
||||
FmtDebug(replay_gain_domain,
|
||||
"replay gain mode has changed {}->{}",
|
||||
ToString(mode), ToString(_mode));
|
||||
|
||||
mode = _mode;
|
||||
Update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the new volume after a property was changed.
|
||||
*/
|
||||
void Update();
|
||||
|
||||
/* virtual methods from class Filter */
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
|
||||
};
|
||||
|
||||
class PreparedReplayGainFilter final : public PreparedFilter {
|
||||
const ReplayGainConfig config;
|
||||
|
||||
/**
|
||||
* If set, then this hardware mixer is used for applying
|
||||
* replay gain, instead of the software volume library.
|
||||
*/
|
||||
Mixer *mixer = nullptr;
|
||||
|
||||
/**
|
||||
* Allow the class to convert to a different #SampleFormat to
|
||||
* preserve quality?
|
||||
*/
|
||||
const bool allow_convert;
|
||||
|
||||
/**
|
||||
* The base volume level for scale=1.0, between 1 and 100
|
||||
* (including).
|
||||
*/
|
||||
unsigned base;
|
||||
|
||||
public:
|
||||
explicit PreparedReplayGainFilter(const ReplayGainConfig _config,
|
||||
bool _allow_convert)
|
||||
:config(_config), allow_convert(_allow_convert) {}
|
||||
|
||||
void SetMixer(Mixer *_mixer, unsigned _base) {
|
||||
assert(_mixer == nullptr || (_base > 0 && _base <= 100));
|
||||
|
||||
mixer = _mixer;
|
||||
base = _base;
|
||||
}
|
||||
|
||||
/* virtual methods from class Filter */
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
void
|
||||
ReplayGainFilter::Update()
|
||||
{
|
||||
unsigned volume = PCM_VOLUME_1;
|
||||
if (mode != ReplayGainMode::OFF) {
|
||||
const auto &tuple = info.Get(mode);
|
||||
float scale = tuple.CalculateScale(config);
|
||||
FmtDebug(replay_gain_domain, "scale={}\n", scale);
|
||||
|
||||
volume = pcm_float_to_volume(scale);
|
||||
}
|
||||
|
||||
if (mixer != nullptr) {
|
||||
/* update the hardware mixer volume */
|
||||
|
||||
unsigned _volume = (volume * base) / PCM_VOLUME_1;
|
||||
if (_volume > 100)
|
||||
_volume = 100;
|
||||
|
||||
try {
|
||||
mixer->LockSetVolume(_volume);
|
||||
|
||||
/* invoke the mixer's listener manually, just
|
||||
in case the mixer implementation didn't do
|
||||
that already (this depends on the
|
||||
implementation) */
|
||||
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception(),
|
||||
"Failed to update hardware mixer");
|
||||
}
|
||||
} else
|
||||
pv.SetVolume(volume);
|
||||
}
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
NewReplayGainFilter(const ReplayGainConfig &config,
|
||||
bool allow_convert) noexcept
|
||||
{
|
||||
return std::make_unique<PreparedReplayGainFilter>(config,
|
||||
allow_convert);
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedReplayGainFilter::Open(AudioFormat &af)
|
||||
{
|
||||
return std::make_unique<ReplayGainFilter>(config, allow_convert,
|
||||
af, mixer, base);
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
ReplayGainFilter::FilterPCM(std::span<const std::byte> src)
|
||||
{
|
||||
return mixer != nullptr
|
||||
? std::span<const std::byte>{src}
|
||||
: pv.Apply(src);
|
||||
}
|
||||
|
||||
void
|
||||
replay_gain_filter_set_mixer(PreparedFilter &_filter, Mixer *mixer,
|
||||
unsigned base)
|
||||
{
|
||||
auto &filter = (PreparedReplayGainFilter &)_filter;
|
||||
|
||||
filter.SetMixer(mixer, base);
|
||||
}
|
||||
|
||||
void
|
||||
replay_gain_filter_set_info(Filter &_filter, const ReplayGainInfo *info)
|
||||
{
|
||||
auto &filter = (ReplayGainFilter &)_filter;
|
||||
|
||||
filter.SetInfo(info);
|
||||
}
|
||||
|
||||
void
|
||||
replay_gain_filter_set_mode(Filter &_filter, ReplayGainMode mode)
|
||||
{
|
||||
auto &filter = (ReplayGainFilter &)_filter;
|
||||
|
||||
filter.SetMode(mode);
|
||||
}
|
||||
@ -1,49 +1,47 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
|
||||
#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
|
||||
|
||||
#include "ReplayGainMode.hxx"
|
||||
|
||||
#include "filter/Prepared.hxx"
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
class Filter;
|
||||
class PreparedFilter;
|
||||
class Mixer;
|
||||
struct ReplayGainConfig;
|
||||
struct ReplayGainInfo;
|
||||
|
||||
/**
|
||||
* @param allow_convert allow the class to convert to a different
|
||||
* #SampleFormat to preserve quality?
|
||||
*/
|
||||
std::unique_ptr<PreparedFilter>
|
||||
NewReplayGainFilter(const ReplayGainConfig &config,
|
||||
bool allow_convert) noexcept;
|
||||
enum class ReplayGainMode : uint8_t;
|
||||
|
||||
/**
|
||||
* Enables or disables the hardware mixer for applying replay gain.
|
||||
*
|
||||
* @param mixer the hardware mixer, or nullptr to fall back to software
|
||||
* volume
|
||||
* @param base the base volume level for scale=1.0, between 1 and 100
|
||||
* (including).
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
void
|
||||
replay_gain_filter_set_mixer(PreparedFilter &_filter, Mixer *mixer,
|
||||
unsigned base);
|
||||
|
||||
/**
|
||||
* Sets a new #ReplayGainInfo at the beginning of a new song.
|
||||
*
|
||||
* @param info the new #ReplayGainInfo value, or nullptr if no replay
|
||||
* gain data is available for the current song
|
||||
*/
|
||||
void
|
||||
replay_gain_filter_set_info(Filter &filter, const ReplayGainInfo *info);
|
||||
|
||||
void
|
||||
replay_gain_filter_set_mode(Filter &filter, ReplayGainMode mode);
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
NewReplayGainFilter([[maybe_unused]] const ReplayGainConfig &config,
|
||||
[[maybe_unused]] bool allow_convert = false)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void
|
||||
replay_gain_filter_set_mode([[maybe_unused]] Filter &,
|
||||
[[maybe_unused]] ReplayGainMode) noexcept
|
||||
{
|
||||
// No-op stub
|
||||
}
|
||||
|
||||
inline void
|
||||
replay_gain_filter_set_info([[maybe_unused]] Filter &,
|
||||
[[maybe_unused]] const void *) noexcept
|
||||
{
|
||||
// No-op stub
|
||||
}
|
||||
|
||||
inline void
|
||||
replay_gain_filter_set_mixer([[maybe_unused]] PreparedFilter &,
|
||||
[[maybe_unused]] class Mixer *,
|
||||
[[maybe_unused]] unsigned) noexcept
|
||||
{
|
||||
// No-op stub
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,260 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
/** \file
|
||||
*
|
||||
* This filter copies audio data between channels. Useful for
|
||||
* upmixing mono/stereo audio to surround speaker configurations.
|
||||
*
|
||||
* Its configuration consists of a "filter" section with a single
|
||||
* "routes" entry, formatted as: \\
|
||||
* routes "0>1, 1>0, 2>2, 3>3, 3>4" \\
|
||||
* where each pair of numbers signifies a set of channels.
|
||||
* Each source>dest pair leads to the data from channel #source
|
||||
* being copied to channel #dest in the output.
|
||||
*
|
||||
* Example: \\
|
||||
* routes "0>0, 1>1, 0>2, 1>3"\\
|
||||
* upmixes stereo audio to a 4-speaker system, copying the front-left
|
||||
* (0) to front left (0) and rear left (2), copying front-right (1) to
|
||||
* front-right (1) and rear-right (3).
|
||||
*
|
||||
* If multiple sources are copied to the same destination channel, only
|
||||
* one of them takes effect.
|
||||
*/
|
||||
|
||||
#include "RouteFilterPlugin.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "filter/FilterPlugin.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Buffer.hxx"
|
||||
#include "pcm/Silence.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
class RouteFilter final : public Filter {
|
||||
/**
|
||||
* The set of copy operations to perform on each sample
|
||||
* The index is an output channel to use, the value is
|
||||
* a corresponding input channel from which to take the
|
||||
* data. A -1 means "no source"
|
||||
*/
|
||||
const std::array<int8_t, MAX_CHANNELS> sources;
|
||||
|
||||
/**
|
||||
* The actual input format of our signal, once opened
|
||||
*/
|
||||
const AudioFormat input_format;
|
||||
|
||||
/**
|
||||
* The size, in bytes, of each multichannel frame in the
|
||||
* input buffer
|
||||
*/
|
||||
const size_t input_frame_size;
|
||||
|
||||
/**
|
||||
* The size, in bytes, of each multichannel frame in the
|
||||
* output buffer
|
||||
*/
|
||||
size_t output_frame_size;
|
||||
|
||||
/**
|
||||
* The output buffer used last time around, can be reused if the size doesn't differ.
|
||||
*/
|
||||
PcmBuffer output_buffer;
|
||||
|
||||
public:
|
||||
RouteFilter(const AudioFormat &audio_format, unsigned out_channels,
|
||||
const std::array<int8_t, MAX_CHANNELS> &_sources);
|
||||
|
||||
/* virtual methods from class Filter */
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
|
||||
};
|
||||
|
||||
class PreparedRouteFilter final : public PreparedFilter {
|
||||
/**
|
||||
* The minimum number of channels we need for output
|
||||
* to be able to perform all the copies the user has specified
|
||||
*/
|
||||
unsigned min_output_channels;
|
||||
|
||||
/**
|
||||
* The minimum number of input channels we need to
|
||||
* copy all the data the user has requested. If fewer
|
||||
* than this many are supplied by the input, undefined
|
||||
* copy operations are given zeroed sources in stead.
|
||||
*/
|
||||
unsigned min_input_channels;
|
||||
|
||||
/**
|
||||
* The set of copy operations to perform on each sample
|
||||
* The index is an output channel to use, the value is
|
||||
* a corresponding input channel from which to take the
|
||||
* data. A -1 means "no source"
|
||||
*/
|
||||
std::array<int8_t, MAX_CHANNELS> sources;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Parse the "routes" section, a string on the form
|
||||
* a>b, c>d, e>f, ...
|
||||
* where a... are non-unique, non-negative integers
|
||||
* and input channel a gets copied to output channel b, etc.
|
||||
* @param block the configuration block to read
|
||||
* @param filter a route_filter whose min_channels and sources[] to set
|
||||
*/
|
||||
explicit PreparedRouteFilter(const ConfigBlock &block);
|
||||
|
||||
/* virtual methods from class PreparedFilter */
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
PreparedRouteFilter::PreparedRouteFilter(const ConfigBlock &block)
|
||||
{
|
||||
/* TODO:
|
||||
* With a more clever way of marking "don't copy to output N",
|
||||
* This could easily be merged into a single loop with some
|
||||
* dynamic realloc() instead of one count run and one malloc().
|
||||
*/
|
||||
|
||||
sources.fill(-1);
|
||||
|
||||
min_input_channels = 0;
|
||||
min_output_channels = 0;
|
||||
|
||||
// A cowardly default, just passthrough stereo
|
||||
const char *routes = block.GetBlockValue("routes", "0>0, 1>1");
|
||||
while (true) {
|
||||
routes = StripLeft(routes);
|
||||
|
||||
char *endptr;
|
||||
const unsigned source = strtoul(routes, &endptr, 10);
|
||||
endptr = StripLeft(endptr);
|
||||
if (endptr == routes || *endptr != '>')
|
||||
throw std::runtime_error("Malformed 'routes' specification");
|
||||
|
||||
if (source >= MAX_CHANNELS)
|
||||
throw FmtRuntimeError("Invalid source channel number: {}",
|
||||
source);
|
||||
|
||||
if (source >= min_input_channels)
|
||||
min_input_channels = source + 1;
|
||||
|
||||
routes = StripLeft(endptr + 1);
|
||||
|
||||
unsigned dest = strtoul(routes, &endptr, 10);
|
||||
endptr = StripLeft(endptr);
|
||||
if (endptr == routes)
|
||||
throw std::runtime_error("Malformed 'routes' specification");
|
||||
|
||||
if (dest >= MAX_CHANNELS)
|
||||
throw FmtRuntimeError("Invalid destination channel number: {}",
|
||||
dest);
|
||||
|
||||
if (dest >= min_output_channels)
|
||||
min_output_channels = dest + 1;
|
||||
|
||||
sources[dest] = source;
|
||||
|
||||
routes = endptr;
|
||||
|
||||
if (*routes == 0)
|
||||
break;
|
||||
|
||||
if (*routes != ',')
|
||||
throw std::runtime_error("Malformed 'routes' specification");
|
||||
|
||||
++routes;
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<PreparedFilter>
|
||||
route_filter_init(const ConfigBlock &block)
|
||||
{
|
||||
return std::make_unique<PreparedRouteFilter>(block);
|
||||
}
|
||||
|
||||
RouteFilter::RouteFilter(const AudioFormat &audio_format,
|
||||
unsigned out_channels,
|
||||
const std::array<int8_t, MAX_CHANNELS> &_sources)
|
||||
:Filter(audio_format), sources(_sources), input_format(audio_format),
|
||||
input_frame_size(input_format.GetFrameSize())
|
||||
{
|
||||
// Decide on an output format which has enough channels,
|
||||
// and is otherwise identical
|
||||
out_audio_format.channels = out_channels;
|
||||
|
||||
// Precalculate this simple value, to speed up allocation later
|
||||
output_frame_size = out_audio_format.GetFrameSize();
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedRouteFilter::Open(AudioFormat &audio_format)
|
||||
{
|
||||
return std::make_unique<RouteFilter>(audio_format, min_output_channels,
|
||||
sources);
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
RouteFilter::FilterPCM(std::span<const std::byte> src)
|
||||
{
|
||||
size_t number_of_frames = src.size() / input_frame_size;
|
||||
|
||||
const size_t bytes_per_frame_per_channel = input_format.GetSampleSize();
|
||||
|
||||
// A moving pointer that always refers to channel 0 in the input, at the currently handled frame
|
||||
const auto *base_source = (const uint8_t *)src.data();
|
||||
|
||||
// Grow our reusable buffer, if needed, and set the moving pointer
|
||||
const size_t result_size = number_of_frames * output_frame_size;
|
||||
void *const result = output_buffer.Get(result_size);
|
||||
|
||||
// A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
|
||||
auto *chan_destination = (std::byte *)result;
|
||||
|
||||
// Perform our copy operations, with N input channels and M output channels
|
||||
for (unsigned int s=0; s<number_of_frames; ++s) {
|
||||
|
||||
// Need to perform one copy per output channel
|
||||
for (unsigned c = 0; c < out_audio_format.channels; ++c) {
|
||||
if (sources[c] == -1 ||
|
||||
(unsigned)sources[c] >= input_format.channels) {
|
||||
// No source for this destination output,
|
||||
// give it zeroes as input
|
||||
PcmSilence({chan_destination, bytes_per_frame_per_channel},
|
||||
input_format.format);
|
||||
} else {
|
||||
// Get the data from channel sources[c]
|
||||
// and copy it to the output
|
||||
const uint8_t *data = base_source +
|
||||
(sources[c] * bytes_per_frame_per_channel);
|
||||
memcpy(chan_destination,
|
||||
data,
|
||||
bytes_per_frame_per_channel);
|
||||
}
|
||||
// Move on to the next output channel
|
||||
chan_destination += bytes_per_frame_per_channel;
|
||||
}
|
||||
|
||||
|
||||
// Go on to the next N input samples
|
||||
base_source += input_frame_size;
|
||||
}
|
||||
|
||||
// Here it is, ladies and gentlemen! Rerouted data!
|
||||
return { (const std::byte *)result, result_size };
|
||||
}
|
||||
|
||||
const FilterPlugin route_filter_plugin = {
|
||||
"route",
|
||||
route_filter_init,
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#ifndef MPD_ROUTE_FILTER_PLUGIN_HXX
|
||||
#define MPD_ROUTE_FILTER_PLUGIN_HXX
|
||||
|
||||
struct FilterPlugin;
|
||||
|
||||
extern const FilterPlugin route_filter_plugin;
|
||||
|
||||
#endif
|
||||
@ -1,100 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "TwoFilters.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.hxx"
|
||||
#include "lib/fmt/RuntimeError.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
void
|
||||
TwoFilters::Reset() noexcept
|
||||
{
|
||||
assert(first);
|
||||
assert(second);
|
||||
|
||||
first->Reset();
|
||||
second->Reset();
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
TwoFilters::FilterPCM(std::span<const std::byte> src)
|
||||
{
|
||||
assert(first);
|
||||
assert(second);
|
||||
|
||||
if (const auto dest = first->FilterPCM(src); dest.empty()) [[unlikely]]
|
||||
/* no output from the first filter; pass the empty
|
||||
buffer on, do not call the second filter */
|
||||
return dest;
|
||||
else
|
||||
/* pass output from the first filter to the second
|
||||
filter and return its result */
|
||||
return second->FilterPCM(dest);
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
TwoFilters::ReadMore()
|
||||
{
|
||||
assert(first);
|
||||
assert(second);
|
||||
|
||||
/* first read all remaining data from the second filter */
|
||||
if (auto result = second->ReadMore(); !result.empty())
|
||||
return result;
|
||||
|
||||
/* now read more data from the first filter and process it
|
||||
with the second filter */
|
||||
if (auto result = first->ReadMore(); !result.empty())
|
||||
/* output from the first Filter must be filtered by
|
||||
the second Filter */
|
||||
return second->FilterPCM(result);
|
||||
|
||||
/* both filters have been queried and there's no more data */
|
||||
return {};
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
TwoFilters::Flush()
|
||||
{
|
||||
assert(second);
|
||||
|
||||
/* first read all remaining data from the second filter */
|
||||
if (auto result = second->ReadMore(); !result.empty())
|
||||
return result;
|
||||
|
||||
/* now flush the first filter and process it with the second
|
||||
filter */
|
||||
if (first) {
|
||||
if (auto result = first->Flush(); !result.empty())
|
||||
/* output from the first Filter must be
|
||||
filtered by the second Filter */
|
||||
return second->FilterPCM(result);
|
||||
|
||||
/* the first filter is flushed completely and we don't
|
||||
need it anymore; any further method calls that
|
||||
would use it are illegal according to the Filter
|
||||
API docs */
|
||||
first.reset();
|
||||
}
|
||||
|
||||
/* finally flush the second filter */
|
||||
return second->Flush();
|
||||
}
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedTwoFilters::Open(AudioFormat &audio_format)
|
||||
{
|
||||
auto a = first->Open(audio_format);
|
||||
|
||||
const auto &a_out_format = a->GetOutAudioFormat();
|
||||
auto b_in_format = a_out_format;
|
||||
auto b = second->Open(b_in_format);
|
||||
|
||||
if (b_in_format != a_out_format)
|
||||
throw FmtRuntimeError("Audio format not supported by filter {:?}: {}",
|
||||
second_name, a_out_format);
|
||||
|
||||
return std::make_unique<TwoFilters>(std::move(a),
|
||||
std::move(b));
|
||||
}
|
||||
@ -1,66 +1,29 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#pragma once
|
||||
#ifndef MPD_TWO_FILTERS_HXX
|
||||
#define MPD_TWO_FILTERS_HXX
|
||||
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* A #Filter implementation which chains two other filters.
|
||||
*/
|
||||
class TwoFilters final : public Filter {
|
||||
std::unique_ptr<Filter> first, second;
|
||||
|
||||
public:
|
||||
template<typename F, typename S>
|
||||
TwoFilters(F &&_first, S &&_second) noexcept
|
||||
:Filter(_second->GetOutAudioFormat()),
|
||||
first(std::forward<F>(_first)),
|
||||
second(std::forward<S>(_second)) {}
|
||||
|
||||
// virtual methods from class Filter
|
||||
void Reset() noexcept override;
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
|
||||
std::span<const std::byte> ReadMore() override;
|
||||
std::span<const std::byte> Flush() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Like #TwoFilters, but implements the #PreparedFilter interface.
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
class PreparedTwoFilters final : public PreparedFilter {
|
||||
std::unique_ptr<PreparedFilter> first, second;
|
||||
std::string second_name;
|
||||
|
||||
public:
|
||||
template<typename F, typename S, typename N>
|
||||
PreparedTwoFilters(F &&_first, S &&_second, N &&_second_name) noexcept
|
||||
:first(std::forward<F>(_first)),
|
||||
second(std::forward<S>(_second)),
|
||||
second_name(std::forward<N>(_second_name)) {}
|
||||
|
||||
std::unique_ptr<Filter> Open(AudioFormat &audio_format) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a #PreparedTwoFilters instance, but only if both parameters
|
||||
* are not nullptr.
|
||||
*/
|
||||
template<typename F, typename S, typename N>
|
||||
static std::unique_ptr<PreparedFilter>
|
||||
ChainFilters(F &&first, S &&second, N &&second_name) noexcept
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
PreparedTwoFilters([[maybe_unused]] std::unique_ptr<PreparedFilter> a,
|
||||
[[maybe_unused]] std::unique_ptr<PreparedFilter> b)
|
||||
{
|
||||
if (!second)
|
||||
return std::forward<F>(first);
|
||||
|
||||
if (!first)
|
||||
return std::forward<S>(second);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<PreparedTwoFilters>(std::forward<F>(first),
|
||||
std::forward<S>(second),
|
||||
std::forward<N>(second_name));
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
ChainFilters([[maybe_unused]] std::unique_ptr<PreparedFilter> a,
|
||||
[[maybe_unused]] std::unique_ptr<PreparedFilter> b,
|
||||
[[maybe_unused]] const char *name = nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
#include "VolumeFilterPlugin.hxx"
|
||||
#include "filter/Filter.hxx"
|
||||
#include "filter/Prepared.hxx"
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
|
||||
class VolumeFilter final : public Filter {
|
||||
PcmVolume pv;
|
||||
|
||||
public:
|
||||
explicit VolumeFilter(const AudioFormat &audio_format)
|
||||
:Filter(audio_format) {
|
||||
out_audio_format.format = pv.Open(out_audio_format.format,
|
||||
true);
|
||||
}
|
||||
|
||||
[[nodiscard]] unsigned GetVolume() const noexcept {
|
||||
return pv.GetVolume();
|
||||
}
|
||||
|
||||
void SetVolume(unsigned _volume) noexcept {
|
||||
pv.SetVolume(_volume);
|
||||
}
|
||||
|
||||
/* virtual methods from class Filter */
|
||||
std::span<const std::byte> FilterPCM(std::span<const std::byte> src) override;
|
||||
};
|
||||
|
||||
class PreparedVolumeFilter final : public PreparedFilter {
|
||||
public:
|
||||
/* virtual methods from class Filter */
|
||||
std::unique_ptr<Filter> Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<Filter>
|
||||
PreparedVolumeFilter::Open(AudioFormat &audio_format)
|
||||
{
|
||||
return std::make_unique<VolumeFilter>(audio_format);
|
||||
}
|
||||
|
||||
std::span<const std::byte>
|
||||
VolumeFilter::FilterPCM(std::span<const std::byte> src)
|
||||
{
|
||||
return pv.Apply(src);
|
||||
}
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
volume_filter_prepare() noexcept
|
||||
{
|
||||
return std::make_unique<PreparedVolumeFilter>();
|
||||
}
|
||||
|
||||
unsigned
|
||||
volume_filter_get(const Filter *_filter) noexcept
|
||||
{
|
||||
const auto *filter =
|
||||
(const VolumeFilter *)_filter;
|
||||
|
||||
return filter->GetVolume();
|
||||
}
|
||||
|
||||
void
|
||||
volume_filter_set(Filter *_filter, unsigned volume) noexcept
|
||||
{
|
||||
auto *filter = (VolumeFilter *)_filter;
|
||||
|
||||
filter->SetVolume(volume);
|
||||
}
|
||||
@ -1,21 +1,28 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
// STUB FILE - Filter support removed for mpd-dbcreate
|
||||
|
||||
#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX
|
||||
#define MPD_VOLUME_FILTER_PLUGIN_HXX
|
||||
|
||||
#include "filter/Prepared.hxx"
|
||||
#include <memory>
|
||||
|
||||
class PreparedFilter;
|
||||
class Filter;
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
volume_filter_prepare() noexcept;
|
||||
/**
|
||||
* Stub implementation - filter support not needed for database creation
|
||||
*/
|
||||
inline void
|
||||
volume_filter_set(Filter *, unsigned) noexcept
|
||||
{
|
||||
// No-op stub
|
||||
}
|
||||
|
||||
unsigned
|
||||
volume_filter_get(const Filter *filter) noexcept;
|
||||
|
||||
void
|
||||
volume_filter_set(Filter *filter, unsigned volume) noexcept;
|
||||
inline std::unique_ptr<PreparedFilter>
|
||||
volume_filter_prepare() noexcept
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
filter_plugins_sources = []
|
||||
filter_plugins_deps = [fmt_dep]
|
||||
|
||||
if libavfilter_dep.found()
|
||||
filter_plugins_sources += [
|
||||
'FfmpegFilter.cxx',
|
||||
'FfmpegFilterPlugin.cxx',
|
||||
'HdcdFilterPlugin.cxx',
|
||||
]
|
||||
filter_plugins_deps += ffmpeg_dep
|
||||
endif
|
||||
|
||||
filter_plugins = static_library(
|
||||
'filter_plugins',
|
||||
'NullFilterPlugin.cxx',
|
||||
'TwoFilters.cxx',
|
||||
'AutoConvertFilterPlugin.cxx',
|
||||
'ConvertFilterPlugin.cxx',
|
||||
'RouteFilterPlugin.cxx',
|
||||
'NormalizeFilterPlugin.cxx',
|
||||
'ReplayGainFilterPlugin.cxx',
|
||||
'VolumeFilterPlugin.cxx',
|
||||
filter_plugins_sources,
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
filter_plugins_deps,
|
||||
log_dep,
|
||||
],
|
||||
)
|
||||
|
||||
filter_plugins_dep = declare_dependency(
|
||||
link_with: filter_plugins,
|
||||
dependencies: [
|
||||
filter_api_dep,
|
||||
pcm_dep,
|
||||
config_dep,
|
||||
] + filter_plugins_deps,
|
||||
)
|
||||
Loading…
Reference in New Issue