encoder/Interface: move instance methods to abstract class
Rename struct Encoder to PreparedEncoder, and add a new (abstract) class Encoder which represents one encoder instance.
This commit is contained in:
parent
69bf835059
commit
e7edc02647
@ -21,30 +21,108 @@
|
||||
#define MPD_ENCODER_INTERFACE_HXX
|
||||
|
||||
#include "EncoderPlugin.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct Encoder {
|
||||
struct Tag;
|
||||
|
||||
class Encoder {
|
||||
const bool implements_tag;
|
||||
|
||||
public:
|
||||
explicit Encoder(bool _implements_tag)
|
||||
:implements_tag(_implements_tag) {}
|
||||
virtual ~Encoder() {}
|
||||
|
||||
bool ImplementsTag() const {
|
||||
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 encoder_read().
|
||||
*
|
||||
* After this function has been called, the encoder may not be
|
||||
* usable for more data, and only Read() and Close() can be
|
||||
* called.
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
virtual bool End(gcc_unused Error &error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes an encoder object, make everything which might
|
||||
* currently be buffered available by Read().
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
virtual bool Flush(gcc_unused Error &error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
virtual bool PreTag(gcc_unused Error &error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a tag to the encoder.
|
||||
*
|
||||
* Instructions: call PreTag(); then obtain flushed data with
|
||||
* Read(); finally call Tag().
|
||||
*
|
||||
* @param tag the tag object
|
||||
* @return true on success
|
||||
*/
|
||||
virtual bool SendTag(gcc_unused const Tag &tag,
|
||||
gcc_unused Error &error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes raw PCM data to the encoder.
|
||||
*
|
||||
* @param data the buffer containing PCM samples
|
||||
* @param length the length of the buffer in bytes
|
||||
* @return true on success
|
||||
*/
|
||||
virtual bool Write(const void *data, size_t length,
|
||||
Error &error) = 0;
|
||||
|
||||
/**
|
||||
* Reads encoded data from the encoder.
|
||||
*
|
||||
* Call this repeatedly until no more data is returned.
|
||||
*
|
||||
* @param dest the destination buffer to copy to
|
||||
* @param length the maximum length of the destination buffer
|
||||
* @return the number of bytes written to #dest
|
||||
*/
|
||||
virtual size_t Read(void *dest, size_t length) = 0;
|
||||
};
|
||||
|
||||
struct PreparedEncoder {
|
||||
const EncoderPlugin &plugin;
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool open, pre_tag, tag, end;
|
||||
#endif
|
||||
|
||||
explicit Encoder(const EncoderPlugin &_plugin)
|
||||
:plugin(_plugin)
|
||||
#ifndef NDEBUG
|
||||
, open(false)
|
||||
#endif
|
||||
{}
|
||||
explicit PreparedEncoder(const EncoderPlugin &_plugin)
|
||||
:plugin(_plugin) {}
|
||||
|
||||
|
||||
/**
|
||||
* Frees an #Encoder object.
|
||||
*/
|
||||
void Dispose() {
|
||||
assert(!open);
|
||||
|
||||
plugin.finish(this);
|
||||
}
|
||||
|
||||
@ -61,191 +139,19 @@ struct Encoder {
|
||||
* may modify the struct to adapt it to its abilities
|
||||
* @return true on success
|
||||
*/
|
||||
bool Open(AudioFormat &audio_format, Error &error) {
|
||||
assert(!open);
|
||||
|
||||
bool success = plugin.open(this, audio_format, error);
|
||||
#ifndef NDEBUG
|
||||
open = success;
|
||||
pre_tag = tag = end = false;
|
||||
#endif
|
||||
return success;
|
||||
Encoder *Open(AudioFormat &audio_format, Error &error) {
|
||||
return plugin.open(this, audio_format, error);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the object. This disables the encoder, and readies
|
||||
* it for reusal by calling Open() again.
|
||||
*/
|
||||
void Close() {
|
||||
assert(open);
|
||||
|
||||
if (plugin.close != nullptr)
|
||||
plugin.close(this);
|
||||
|
||||
#ifndef NDEBUG
|
||||
open = false;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ends the stream: flushes the encoder object, generate an
|
||||
* end-of-stream marker (if applicable), make everything which might
|
||||
* currently be buffered available by encoder_read().
|
||||
*
|
||||
* After this function has been called, the encoder may not be usable
|
||||
* for more data, and only encoder_read() and Encoder::Close() can be
|
||||
* called.
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @return true on success
|
||||
*/
|
||||
static inline bool
|
||||
encoder_end(Encoder *encoder, Error &error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->end);
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->end = true;
|
||||
#endif
|
||||
|
||||
/* this method is optional */
|
||||
return encoder->plugin.end != nullptr
|
||||
? encoder->plugin.end(encoder, error)
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes an encoder object, make everything which might currently be
|
||||
* buffered available by encoder_read().
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @return true on success
|
||||
*/
|
||||
static inline bool
|
||||
encoder_flush(Encoder *encoder, Error &error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(!encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
/* this method is optional */
|
||||
return encoder->plugin.flush != nullptr
|
||||
? encoder->plugin.flush(encoder, error)
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @return true on success
|
||||
*/
|
||||
static inline bool
|
||||
encoder_pre_tag(Encoder *encoder, Error &error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(!encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
/* this method is optional */
|
||||
bool success = encoder->plugin.pre_tag != nullptr
|
||||
? encoder->plugin.pre_tag(encoder, error)
|
||||
: true;
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->pre_tag = success;
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a tag to the encoder.
|
||||
*
|
||||
* Instructions: call encoder_pre_tag(); then obtain flushed data with
|
||||
* encoder_read(); finally call encoder_tag().
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @param tag the tag object
|
||||
* @return true on success
|
||||
*/
|
||||
static inline bool
|
||||
encoder_tag(Encoder *encoder, const Tag &tag, Error &error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->tag = false;
|
||||
#endif
|
||||
|
||||
/* this method is optional */
|
||||
return encoder->plugin.tag != nullptr
|
||||
? encoder->plugin.tag(encoder, tag, error)
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes raw PCM data to the encoder.
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @param data the buffer containing PCM samples
|
||||
* @param length the length of the buffer in bytes
|
||||
* @return true on success
|
||||
*/
|
||||
static inline bool
|
||||
encoder_write(Encoder *encoder, const void *data, size_t length,
|
||||
Error &error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(!encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
return encoder->plugin.write(encoder, data, length, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads encoded data from the encoder.
|
||||
*
|
||||
* Call this repeatedly until no more data is returned.
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @param dest the destination buffer to copy to
|
||||
* @param length the maximum length of the destination buffer
|
||||
* @return the number of bytes written to #dest
|
||||
*/
|
||||
static inline size_t
|
||||
encoder_read(Encoder *encoder, void *dest, size_t length)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag || !encoder->tag);
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (encoder->pre_tag) {
|
||||
encoder->pre_tag = false;
|
||||
encoder->tag = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return encoder->plugin.read(encoder, dest, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mime type of encoded content.
|
||||
*
|
||||
* @return an constant string, nullptr on failure
|
||||
*/
|
||||
static inline const char *
|
||||
encoder_get_mime_type(Encoder *encoder)
|
||||
encoder_get_mime_type(PreparedEncoder *encoder)
|
||||
{
|
||||
/* this method is optional */
|
||||
return encoder->plugin.get_mime_type != nullptr
|
||||
|
@ -20,44 +20,25 @@
|
||||
#ifndef MPD_ENCODER_PLUGIN_HXX
|
||||
#define MPD_ENCODER_PLUGIN_HXX
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct Encoder;
|
||||
struct PreparedEncoder;
|
||||
class Encoder;
|
||||
struct AudioFormat;
|
||||
struct ConfigBlock;
|
||||
struct Tag;
|
||||
class Error;
|
||||
|
||||
struct EncoderPlugin {
|
||||
const char *name;
|
||||
|
||||
Encoder *(*init)(const ConfigBlock &block,
|
||||
PreparedEncoder *(*init)(const ConfigBlock &block,
|
||||
Error &error);
|
||||
|
||||
void (*finish)(PreparedEncoder *encoder);
|
||||
|
||||
Encoder *(*open)(PreparedEncoder *encoder,
|
||||
AudioFormat &audio_format,
|
||||
Error &error);
|
||||
|
||||
void (*finish)(Encoder *encoder);
|
||||
|
||||
bool (*open)(Encoder *encoder,
|
||||
AudioFormat &audio_format,
|
||||
Error &error);
|
||||
|
||||
void (*close)(Encoder *encoder);
|
||||
|
||||
bool (*end)(Encoder *encoder, Error &error);
|
||||
|
||||
bool (*flush)(Encoder *encoder, Error &error);
|
||||
|
||||
bool (*pre_tag)(Encoder *encoder, Error &error);
|
||||
|
||||
bool (*tag)(Encoder *encoder, const Tag &tag,
|
||||
Error &error);
|
||||
|
||||
bool (*write)(Encoder *encoder,
|
||||
const void *data, size_t length,
|
||||
Error &error);
|
||||
|
||||
size_t (*read)(Encoder *encoder, void *dest, size_t length);
|
||||
|
||||
const char *(*get_mime_type)(Encoder *encoder);
|
||||
const char *(*get_mime_type)(PreparedEncoder *encoder);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -67,7 +48,7 @@ struct EncoderPlugin {
|
||||
* @param error location to store the error occurring, or nullptr to ignore errors.
|
||||
* @return an encoder object on success, nullptr on failure
|
||||
*/
|
||||
static inline Encoder *
|
||||
static inline PreparedEncoder *
|
||||
encoder_init(const EncoderPlugin &plugin, const ConfigBlock &block,
|
||||
Error &error)
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ EncoderToOutputStream(OutputStream &os, Encoder &encoder)
|
||||
/* read from the encoder */
|
||||
|
||||
char buffer[32768];
|
||||
size_t nbytes = encoder_read(&encoder, buffer, sizeof(buffer));
|
||||
size_t nbytes = encoder.Read(buffer, sizeof(buffer));
|
||||
if (nbytes == 0)
|
||||
return;
|
||||
|
||||
|
@ -22,8 +22,8 @@
|
||||
|
||||
#include "check.h"
|
||||
|
||||
struct Encoder;
|
||||
class OutputStream;
|
||||
class Encoder;
|
||||
|
||||
void
|
||||
EncoderToOutputStream(OutputStream &os, Encoder &encoder);
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "AudioFormat.hxx"
|
||||
#include "pcm/PcmBuffer.hxx"
|
||||
#include "config/ConfigError.hxx"
|
||||
#include "util/Manual.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
@ -34,13 +33,10 @@
|
||||
#error libFLAC is too old
|
||||
#endif
|
||||
|
||||
struct flac_encoder {
|
||||
Encoder encoder;
|
||||
class FlacEncoder final : public Encoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
AudioFormat audio_format;
|
||||
unsigned compression;
|
||||
|
||||
FLAC__StreamEncoder *fse;
|
||||
FLAC__StreamEncoder *const fse;
|
||||
|
||||
PcmBuffer expand_buffer;
|
||||
|
||||
@ -48,29 +44,76 @@ struct flac_encoder {
|
||||
* This buffer will hold encoded data from libFLAC until it is
|
||||
* picked up with flac_encoder_read().
|
||||
*/
|
||||
Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
|
||||
DynamicFifoBuffer<uint8_t> output_buffer;
|
||||
|
||||
flac_encoder():encoder(flac_encoder_plugin) {}
|
||||
public:
|
||||
FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse)
|
||||
:Encoder(false),
|
||||
audio_format(_audio_format), fse(_fse),
|
||||
output_buffer(8192) {}
|
||||
|
||||
~FlacEncoder() override {
|
||||
FLAC__stream_encoder_delete(fse);
|
||||
}
|
||||
|
||||
bool Init(Error &error);
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
bool End(Error &) override {
|
||||
(void) FLAC__stream_encoder_finish(fse);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Flush(Error &) override {
|
||||
(void) FLAC__stream_encoder_finish(fse);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Write(const void *data, size_t length, Error &) override;
|
||||
|
||||
size_t Read(void *dest, size_t length) override {
|
||||
return output_buffer.Read((uint8_t *)dest, length);
|
||||
}
|
||||
|
||||
private:
|
||||
static FLAC__StreamEncoderWriteStatus WriteCallback(const FLAC__StreamEncoder *,
|
||||
const FLAC__byte data[],
|
||||
size_t bytes,
|
||||
gcc_unused unsigned samples,
|
||||
gcc_unused unsigned current_frame,
|
||||
void *client_data) {
|
||||
auto &encoder = *(FlacEncoder *)client_data;
|
||||
encoder.output_buffer.Append((const uint8_t *)data, bytes);
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
struct PreparedFlacEncoder {
|
||||
PreparedEncoder encoder;
|
||||
|
||||
unsigned compression;
|
||||
|
||||
PreparedFlacEncoder():encoder(flac_encoder_plugin) {}
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
};
|
||||
|
||||
static constexpr Domain flac_encoder_domain("vorbis_encoder");
|
||||
|
||||
static bool
|
||||
flac_encoder_configure(struct flac_encoder *encoder, const ConfigBlock &block,
|
||||
gcc_unused Error &error)
|
||||
bool
|
||||
PreparedFlacEncoder::Configure(const ConfigBlock &block, Error &)
|
||||
{
|
||||
encoder->compression = block.GetBlockValue("compression", 5u);
|
||||
|
||||
compression = block.GetBlockValue("compression", 5u);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
flac_encoder_init(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
flac_encoder *encoder = new flac_encoder();
|
||||
auto *encoder = new PreparedFlacEncoder();
|
||||
|
||||
/* load configuration from "block" */
|
||||
if (!flac_encoder_configure(encoder, block, error)) {
|
||||
if (!encoder->Configure(block, error)) {
|
||||
/* configuration has failed, roll back and return error */
|
||||
delete encoder;
|
||||
return nullptr;
|
||||
@ -80,9 +123,9 @@ flac_encoder_init(const ConfigBlock &block, Error &error)
|
||||
}
|
||||
|
||||
static void
|
||||
flac_encoder_finish(Encoder *_encoder)
|
||||
flac_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
auto *encoder = (PreparedFlacEncoder *)_encoder;
|
||||
|
||||
/* the real libFLAC cleanup was already performed by
|
||||
flac_encoder_close(), so no real work here */
|
||||
@ -90,71 +133,67 @@ flac_encoder_finish(Encoder *_encoder)
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample,
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||
const AudioFormat &audio_format, unsigned bits_per_sample,
|
||||
Error &error)
|
||||
{
|
||||
if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
|
||||
encoder->compression)) {
|
||||
if (!FLAC__stream_encoder_set_compression_level(fse, compression)) {
|
||||
error.Format(config_domain,
|
||||
"error setting flac compression to %d",
|
||||
encoder->compression);
|
||||
compression);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !FLAC__stream_encoder_set_channels(encoder->fse,
|
||||
encoder->audio_format.channels)) {
|
||||
if (!FLAC__stream_encoder_set_channels(fse, audio_format.channels)) {
|
||||
error.Format(config_domain,
|
||||
"error setting flac channels num to %d",
|
||||
encoder->audio_format.channels);
|
||||
audio_format.channels);
|
||||
return false;
|
||||
}
|
||||
if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse,
|
||||
bits_per_sample)) {
|
||||
|
||||
if (!FLAC__stream_encoder_set_bits_per_sample(fse, bits_per_sample)) {
|
||||
error.Format(config_domain,
|
||||
"error setting flac bit format to %d",
|
||||
bits_per_sample);
|
||||
return false;
|
||||
}
|
||||
if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse,
|
||||
encoder->audio_format.sample_rate)) {
|
||||
|
||||
if (!FLAC__stream_encoder_set_sample_rate(fse,
|
||||
audio_format.sample_rate)) {
|
||||
error.Format(config_domain,
|
||||
"error setting flac sample rate to %d",
|
||||
encoder->audio_format.sample_rate);
|
||||
audio_format.sample_rate);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static FLAC__StreamEncoderWriteStatus
|
||||
flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse,
|
||||
const FLAC__byte data[],
|
||||
size_t bytes,
|
||||
gcc_unused unsigned samples,
|
||||
gcc_unused unsigned current_frame, void *client_data)
|
||||
bool
|
||||
FlacEncoder::Init(Error &error)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *) client_data;
|
||||
/* this immediately outputs data through callback */
|
||||
|
||||
//transfer data to buffer
|
||||
encoder->output_buffer->Append((const uint8_t *)data, bytes);
|
||||
auto init_status =
|
||||
FLAC__stream_encoder_init_stream(fse,
|
||||
WriteCallback,
|
||||
nullptr, nullptr, nullptr,
|
||||
this);
|
||||
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
|
||||
error.Format(flac_encoder_domain,
|
||||
"failed to initialize encoder: %s\n",
|
||||
FLAC__StreamEncoderInitStatusString[init_status]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_encoder_close(Encoder *_encoder)
|
||||
static Encoder *
|
||||
flac_encoder_open(PreparedEncoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
|
||||
FLAC__stream_encoder_delete(encoder->fse);
|
||||
|
||||
encoder->expand_buffer.Clear();
|
||||
encoder->output_buffer.Destruct();
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
auto *encoder = (PreparedFlacEncoder *)_encoder;
|
||||
unsigned bits_per_sample;
|
||||
|
||||
/* FIXME: flac should support 32bit as well */
|
||||
@ -176,51 +215,26 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
audio_format.format = SampleFormat::S24_P32;
|
||||
}
|
||||
|
||||
encoder->audio_format = audio_format;
|
||||
|
||||
/* allocate the encoder */
|
||||
encoder->fse = FLAC__stream_encoder_new();
|
||||
if (encoder->fse == nullptr) {
|
||||
error.Set(flac_encoder_domain, "flac_new() failed");
|
||||
return false;
|
||||
auto fse = FLAC__stream_encoder_new();
|
||||
if (fse == nullptr) {
|
||||
error.Set(flac_encoder_domain, "FLAC__stream_encoder_new() failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!flac_encoder_setup(encoder, bits_per_sample, error)) {
|
||||
FLAC__stream_encoder_delete(encoder->fse);
|
||||
return false;
|
||||
if (!flac_encoder_setup(fse, encoder->compression,
|
||||
audio_format, bits_per_sample, error)) {
|
||||
FLAC__stream_encoder_delete(fse);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
encoder->output_buffer.Construct(8192);
|
||||
|
||||
/* this immediately outputs data through callback */
|
||||
|
||||
{
|
||||
FLAC__StreamEncoderInitStatus init_status;
|
||||
|
||||
init_status = FLAC__stream_encoder_init_stream(encoder->fse,
|
||||
flac_write_callback,
|
||||
nullptr, nullptr, nullptr, encoder);
|
||||
|
||||
if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
|
||||
error.Format(flac_encoder_domain,
|
||||
"failed to initialize encoder: %s\n",
|
||||
FLAC__StreamEncoderInitStatusString[init_status]);
|
||||
flac_encoder_close(_encoder);
|
||||
return false;
|
||||
}
|
||||
auto *e = new FlacEncoder(audio_format, fse);
|
||||
if (!e->Init(error)) {
|
||||
delete e;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
flac_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
|
||||
(void) FLAC__stream_encoder_finish(encoder->fse);
|
||||
return true;
|
||||
return e;
|
||||
}
|
||||
|
||||
static inline void
|
||||
@ -241,31 +255,27 @@ pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples)
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_encoder_write(Encoder *_encoder,
|
||||
const void *data, size_t length,
|
||||
gcc_unused Error &error)
|
||||
bool
|
||||
FlacEncoder::Write(const void *data, size_t length, Error &error)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
unsigned num_frames, num_samples;
|
||||
void *exbuffer;
|
||||
const void *buffer = nullptr;
|
||||
|
||||
/* format conversion */
|
||||
|
||||
num_frames = length / encoder->audio_format.GetFrameSize();
|
||||
num_samples = num_frames * encoder->audio_format.channels;
|
||||
const unsigned num_frames = length / audio_format.GetFrameSize();
|
||||
const unsigned num_samples = num_frames * audio_format.channels;
|
||||
|
||||
switch (encoder->audio_format.format) {
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S8:
|
||||
exbuffer = encoder->expand_buffer.Get(length * 4);
|
||||
exbuffer = expand_buffer.Get(length * 4);
|
||||
pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data,
|
||||
num_samples);
|
||||
buffer = exbuffer;
|
||||
break;
|
||||
|
||||
case SampleFormat::S16:
|
||||
exbuffer = encoder->expand_buffer.Get(length * 2);
|
||||
exbuffer = expand_buffer.Get(length * 2);
|
||||
pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data,
|
||||
num_samples);
|
||||
buffer = exbuffer;
|
||||
@ -284,7 +294,7 @@ flac_encoder_write(Encoder *_encoder,
|
||||
|
||||
/* feed samples to encoder */
|
||||
|
||||
if (!FLAC__stream_encoder_process_interleaved(encoder->fse,
|
||||
if (!FLAC__stream_encoder_process_interleaved(fse,
|
||||
(const FLAC__int32 *)buffer,
|
||||
num_frames)) {
|
||||
error.Set(flac_encoder_domain, "flac encoder process failed");
|
||||
@ -294,16 +304,8 @@ flac_encoder_write(Encoder *_encoder,
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
flac_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
|
||||
return encoder->output_buffer->Read((uint8_t *)dest, length);
|
||||
}
|
||||
|
||||
static const char *
|
||||
flac_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
||||
flac_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||
{
|
||||
return "audio/flac";
|
||||
}
|
||||
@ -313,13 +315,6 @@ const EncoderPlugin flac_encoder_plugin = {
|
||||
flac_encoder_init,
|
||||
flac_encoder_finish,
|
||||
flac_encoder_open,
|
||||
flac_encoder_close,
|
||||
flac_encoder_flush,
|
||||
flac_encoder_flush,
|
||||
nullptr,
|
||||
nullptr,
|
||||
flac_encoder_write,
|
||||
flac_encoder_read,
|
||||
flac_encoder_get_mime_type,
|
||||
};
|
||||
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include "config/ConfigError.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/Manual.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
@ -33,19 +32,34 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
struct LameEncoder final {
|
||||
Encoder encoder;
|
||||
class LameEncoder final : public Encoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
lame_global_flags *const gfp;
|
||||
|
||||
ReusableArray<unsigned char, 32768> output_buffer;
|
||||
unsigned char *output_begin = nullptr, *output_end = nullptr;
|
||||
|
||||
public:
|
||||
LameEncoder(const AudioFormat _audio_format,
|
||||
lame_global_flags *_gfp)
|
||||
:Encoder(false),
|
||||
audio_format(_audio_format), gfp(_gfp) {}
|
||||
|
||||
~LameEncoder() override;
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
bool Write(const void *data, size_t length, Error &) override;
|
||||
size_t Read(void *dest, size_t length) override;
|
||||
};
|
||||
|
||||
struct PreparedLameEncoder final {
|
||||
PreparedEncoder encoder;
|
||||
|
||||
AudioFormat audio_format;
|
||||
float quality;
|
||||
int bitrate;
|
||||
|
||||
lame_global_flags *gfp;
|
||||
|
||||
Manual<ReusableArray<unsigned char, 32768>> output_buffer;
|
||||
unsigned char *output_begin, *output_end;
|
||||
|
||||
LameEncoder():encoder(lame_encoder_plugin) {}
|
||||
PreparedLameEncoder():encoder(lame_encoder_plugin) {}
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
};
|
||||
@ -53,7 +67,7 @@ struct LameEncoder final {
|
||||
static constexpr Domain lame_encoder_domain("lame_encoder");
|
||||
|
||||
bool
|
||||
LameEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
PreparedLameEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
const char *value;
|
||||
char *endptr;
|
||||
@ -100,10 +114,10 @@ LameEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
lame_encoder_init(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
LameEncoder *encoder = new LameEncoder();
|
||||
auto *encoder = new PreparedLameEncoder();
|
||||
|
||||
/* load configuration from "block" */
|
||||
if (!encoder->Configure(block, error)) {
|
||||
@ -116,9 +130,9 @@ lame_encoder_init(const ConfigBlock &block, Error &error)
|
||||
}
|
||||
|
||||
static void
|
||||
lame_encoder_finish(Encoder *_encoder)
|
||||
lame_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
LameEncoder *encoder = (LameEncoder *)_encoder;
|
||||
auto *encoder = (PreparedLameEncoder *)_encoder;
|
||||
|
||||
/* the real liblame cleanup was already performed by
|
||||
lame_encoder_close(), so no real work here */
|
||||
@ -126,17 +140,18 @@ lame_encoder_finish(Encoder *_encoder)
|
||||
}
|
||||
|
||||
static bool
|
||||
lame_encoder_setup(LameEncoder *encoder, Error &error)
|
||||
lame_encoder_setup(lame_global_flags *gfp, float quality, int bitrate,
|
||||
const AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
if (encoder->quality >= -1.0) {
|
||||
if (quality >= -1.0) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) {
|
||||
if (0 != lame_set_VBR(gfp, vbr_rh)) {
|
||||
error.Set(lame_encoder_domain,
|
||||
"error setting lame VBR mode");
|
||||
return false;
|
||||
}
|
||||
if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) {
|
||||
if (0 != lame_set_VBR_q(gfp, quality)) {
|
||||
error.Set(lame_encoder_domain,
|
||||
"error setting lame VBR quality");
|
||||
return false;
|
||||
@ -144,35 +159,32 @@ lame_encoder_setup(LameEncoder *encoder, Error &error)
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) {
|
||||
if (0 != lame_set_brate(gfp, bitrate)) {
|
||||
error.Set(lame_encoder_domain,
|
||||
"error setting lame bitrate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 != lame_set_num_channels(encoder->gfp,
|
||||
encoder->audio_format.channels)) {
|
||||
if (0 != lame_set_num_channels(gfp, audio_format.channels)) {
|
||||
error.Set(lame_encoder_domain,
|
||||
"error setting lame num channels");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != lame_set_in_samplerate(encoder->gfp,
|
||||
encoder->audio_format.sample_rate)) {
|
||||
if (0 != lame_set_in_samplerate(gfp, audio_format.sample_rate)) {
|
||||
error.Set(lame_encoder_domain,
|
||||
"error setting lame sample rate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != lame_set_out_samplerate(encoder->gfp,
|
||||
encoder->audio_format.sample_rate)) {
|
||||
if (0 != lame_set_out_samplerate(gfp, audio_format.sample_rate)) {
|
||||
error.Set(lame_encoder_domain,
|
||||
"error setting lame out sample rate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 > lame_init_params(encoder->gfp)) {
|
||||
if (0 > lame_init_params(gfp)) {
|
||||
error.Set(lame_encoder_domain,
|
||||
"error initializing lame params");
|
||||
return false;
|
||||
@ -181,98 +193,84 @@ lame_encoder_setup(LameEncoder *encoder, Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
static Encoder *
|
||||
lame_encoder_open(PreparedEncoder *_encoder, AudioFormat &audio_format,
|
||||
Error &error)
|
||||
{
|
||||
LameEncoder *encoder = (LameEncoder *)_encoder;
|
||||
auto *encoder = (PreparedLameEncoder *)_encoder;
|
||||
|
||||
audio_format.format = SampleFormat::S16;
|
||||
audio_format.channels = 2;
|
||||
|
||||
encoder->audio_format = audio_format;
|
||||
|
||||
encoder->gfp = lame_init();
|
||||
if (encoder->gfp == nullptr) {
|
||||
auto gfp = lame_init();
|
||||
if (gfp == nullptr) {
|
||||
error.Set(lame_encoder_domain, "lame_init() failed");
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!lame_encoder_setup(encoder, error)) {
|
||||
lame_close(encoder->gfp);
|
||||
return false;
|
||||
if (!lame_encoder_setup(gfp, encoder->quality, encoder->bitrate,
|
||||
audio_format, error)) {
|
||||
lame_close(gfp);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
encoder->output_buffer.Construct();
|
||||
encoder->output_begin = encoder->output_end = nullptr;
|
||||
|
||||
return true;
|
||||
return new LameEncoder(audio_format, gfp);
|
||||
}
|
||||
|
||||
static void
|
||||
lame_encoder_close(Encoder *_encoder)
|
||||
LameEncoder::~LameEncoder()
|
||||
{
|
||||
LameEncoder *encoder = (LameEncoder *)_encoder;
|
||||
|
||||
lame_close(encoder->gfp);
|
||||
encoder->output_buffer.Destruct();
|
||||
lame_close(gfp);
|
||||
}
|
||||
|
||||
static bool
|
||||
lame_encoder_write(Encoder *_encoder,
|
||||
const void *data, size_t length,
|
||||
bool
|
||||
LameEncoder::Write(const void *data, size_t length,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
LameEncoder *encoder = (LameEncoder *)_encoder;
|
||||
const int16_t *src = (const int16_t*)data;
|
||||
|
||||
assert(encoder->output_begin == encoder->output_end);
|
||||
assert(output_begin == output_end);
|
||||
|
||||
const unsigned num_frames =
|
||||
length / encoder->audio_format.GetFrameSize();
|
||||
const unsigned num_samples =
|
||||
length / encoder->audio_format.GetSampleSize();
|
||||
const unsigned num_frames = length / audio_format.GetFrameSize();
|
||||
const unsigned num_samples = length / audio_format.GetSampleSize();
|
||||
|
||||
/* worst-case formula according to LAME documentation */
|
||||
const size_t output_buffer_size = 5 * num_samples / 4 + 7200;
|
||||
const auto output_buffer = encoder->output_buffer->Get(output_buffer_size);
|
||||
const auto dest = output_buffer.Get(output_buffer_size);
|
||||
|
||||
/* this is for only 16-bit audio */
|
||||
|
||||
int bytes_out = lame_encode_buffer_interleaved(encoder->gfp,
|
||||
int bytes_out = lame_encode_buffer_interleaved(gfp,
|
||||
const_cast<short *>(src),
|
||||
num_frames,
|
||||
output_buffer,
|
||||
output_buffer_size);
|
||||
dest, output_buffer_size);
|
||||
|
||||
if (bytes_out < 0) {
|
||||
error.Set(lame_encoder_domain, "lame encoder failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder->output_begin = output_buffer;
|
||||
encoder->output_end = output_buffer + bytes_out;
|
||||
output_begin = dest;
|
||||
output_end = dest + bytes_out;
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
lame_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
size_t
|
||||
LameEncoder::Read(void *dest, size_t length)
|
||||
{
|
||||
LameEncoder *encoder = (LameEncoder *)_encoder;
|
||||
|
||||
const auto begin = encoder->output_begin;
|
||||
assert(begin <= encoder->output_end);
|
||||
const size_t remainning = encoder->output_end - begin;
|
||||
const auto begin = output_begin;
|
||||
assert(begin <= output_end);
|
||||
const size_t remainning = output_end - begin;
|
||||
if (length > remainning)
|
||||
length = remainning;
|
||||
|
||||
memcpy(dest, begin, length);
|
||||
|
||||
encoder->output_begin = begin + length;
|
||||
output_begin = begin + length;
|
||||
return length;
|
||||
}
|
||||
|
||||
static const char *
|
||||
lame_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
||||
lame_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||
{
|
||||
return "audio/mpeg";
|
||||
}
|
||||
@ -282,12 +280,5 @@ const EncoderPlugin lame_encoder_plugin = {
|
||||
lame_encoder_init,
|
||||
lame_encoder_finish,
|
||||
lame_encoder_open,
|
||||
lame_encoder_close,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
lame_encoder_write,
|
||||
lame_encoder_read,
|
||||
lame_encoder_get_mime_type,
|
||||
};
|
||||
|
@ -20,73 +20,58 @@
|
||||
#include "config.h"
|
||||
#include "NullEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "util/Manual.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
struct NullEncoder final {
|
||||
Encoder encoder;
|
||||
|
||||
Manual<DynamicFifoBuffer<uint8_t>> buffer;
|
||||
class NullEncoder final : public Encoder {
|
||||
DynamicFifoBuffer<uint8_t> buffer;
|
||||
|
||||
public:
|
||||
NullEncoder()
|
||||
:Encoder(false),
|
||||
buffer(8192) {}
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
bool Write(const void *data, size_t length, Error &) override {
|
||||
buffer.Append((const uint8_t *)data, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t Read(void *dest, size_t length) override {
|
||||
return buffer.Read((uint8_t *)dest, length);
|
||||
}
|
||||
};
|
||||
|
||||
struct PreparedNullEncoder final {
|
||||
PreparedEncoder encoder;
|
||||
|
||||
PreparedNullEncoder()
|
||||
:encoder(null_encoder_plugin) {}
|
||||
};
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
null_encoder_init(gcc_unused const ConfigBlock &block,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
NullEncoder *encoder = new NullEncoder();
|
||||
auto *encoder = new PreparedNullEncoder();
|
||||
return &encoder->encoder;
|
||||
}
|
||||
|
||||
static void
|
||||
null_encoder_finish(Encoder *_encoder)
|
||||
null_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
NullEncoder *encoder = (NullEncoder *)_encoder;
|
||||
|
||||
auto *encoder = (PreparedNullEncoder *)_encoder;
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
static void
|
||||
null_encoder_close(Encoder *_encoder)
|
||||
{
|
||||
NullEncoder *encoder = (NullEncoder *)_encoder;
|
||||
|
||||
encoder->buffer.Destruct();
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
null_encoder_open(Encoder *_encoder,
|
||||
static Encoder *
|
||||
null_encoder_open(gcc_unused PreparedEncoder *encoder,
|
||||
gcc_unused AudioFormat &audio_format,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
NullEncoder *encoder = (NullEncoder *)_encoder;
|
||||
encoder->buffer.Construct(8192);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
null_encoder_write(Encoder *_encoder,
|
||||
const void *data, size_t length,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
NullEncoder *encoder = (NullEncoder *)_encoder;
|
||||
|
||||
encoder->buffer->Append((const uint8_t *)data, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t
|
||||
null_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
NullEncoder *encoder = (NullEncoder *)_encoder;
|
||||
|
||||
return encoder->buffer->Read((uint8_t *)dest, length);
|
||||
return new NullEncoder();
|
||||
}
|
||||
|
||||
const EncoderPlugin null_encoder_plugin = {
|
||||
@ -94,12 +79,5 @@ const EncoderPlugin null_encoder_plugin = {
|
||||
null_encoder_init,
|
||||
null_encoder_finish,
|
||||
null_encoder_open,
|
||||
null_encoder_close,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
null_encoder_write,
|
||||
null_encoder_read,
|
||||
nullptr,
|
||||
};
|
||||
|
@ -35,26 +35,18 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct OpusEncoder {
|
||||
/** the base class */
|
||||
Encoder encoder;
|
||||
namespace {
|
||||
|
||||
/* configuration */
|
||||
class OpusEncoder final : public Encoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
opus_int32 bitrate;
|
||||
int complexity;
|
||||
int signal;
|
||||
const size_t frame_size;
|
||||
|
||||
/* runtime information */
|
||||
const size_t buffer_frames, buffer_size;
|
||||
size_t buffer_position = 0;
|
||||
uint8_t *const buffer;
|
||||
|
||||
AudioFormat audio_format;
|
||||
|
||||
size_t frame_size;
|
||||
|
||||
size_t buffer_frames, buffer_size, buffer_position;
|
||||
uint8_t *buffer;
|
||||
|
||||
OpusEncoder *enc;
|
||||
::OpusEncoder *const enc;
|
||||
|
||||
unsigned char buffer2[1275 * 3 + 7];
|
||||
|
||||
@ -62,35 +54,49 @@ struct OpusEncoder {
|
||||
|
||||
int lookahead;
|
||||
|
||||
ogg_int64_t packetno;
|
||||
ogg_int64_t packetno = 0;
|
||||
|
||||
ogg_int64_t granulepos;
|
||||
|
||||
OpusEncoder():encoder(opus_encoder_plugin), granulepos(0) {}
|
||||
public:
|
||||
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc);
|
||||
~OpusEncoder() override;
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
bool Open(AudioFormat &audio_format, Error &error);
|
||||
void Close();
|
||||
/* virtual methods from class Encoder */
|
||||
bool End(Error &) override;
|
||||
bool Flush(Error &) override;
|
||||
bool Write(const void *data, size_t length, Error &) override;
|
||||
|
||||
size_t Read(void *dest, size_t length) override;
|
||||
|
||||
private:
|
||||
bool DoEncode(bool eos, Error &error);
|
||||
|
||||
bool End(Error &error);
|
||||
bool Flush(Error &error);
|
||||
|
||||
bool WriteSilence(unsigned fill_frames, Error &error);
|
||||
|
||||
bool Write(const void *_data, size_t length, Error &error);
|
||||
|
||||
void GenerateHead();
|
||||
void GenerateTags();
|
||||
};
|
||||
|
||||
size_t Read(void *dest, size_t length);
|
||||
struct PreparedOpusEncoder {
|
||||
/** the base class */
|
||||
PreparedEncoder encoder;
|
||||
|
||||
/* configuration */
|
||||
|
||||
opus_int32 bitrate;
|
||||
int complexity;
|
||||
int signal;
|
||||
|
||||
PreparedOpusEncoder():encoder(opus_encoder_plugin) {}
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
Encoder *Open(AudioFormat &audio_format, Error &error);
|
||||
};
|
||||
|
||||
static constexpr Domain opus_encoder_domain("opus_encoder");
|
||||
|
||||
bool
|
||||
OpusEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
PreparedOpusEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
const char *value = block.GetBlockValue("bitrate", "auto");
|
||||
if (strcmp(value, "auto") == 0)
|
||||
@ -128,10 +134,10 @@ OpusEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
opus_encoder_init(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
auto *encoder = new OpusEncoder();
|
||||
auto *encoder = new PreparedOpusEncoder();
|
||||
|
||||
/* load configuration from "block" */
|
||||
if (!encoder->Configure(block, error)) {
|
||||
@ -144,93 +150,86 @@ opus_encoder_init(const ConfigBlock &block, Error &error)
|
||||
}
|
||||
|
||||
static void
|
||||
opus_encoder_finish(Encoder *_encoder)
|
||||
opus_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
auto *encoder = (OpusEncoder *)_encoder;
|
||||
auto *encoder = (PreparedOpusEncoder *)_encoder;
|
||||
|
||||
/* the real libopus cleanup was already performed by
|
||||
opus_encoder_close(), so no real work here */
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
bool
|
||||
OpusEncoder::Open(AudioFormat &_audio_format, Error &error)
|
||||
OpusEncoder::OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc)
|
||||
:Encoder(false),
|
||||
audio_format(_audio_format),
|
||||
frame_size(_audio_format.GetFrameSize()),
|
||||
buffer_frames(_audio_format.sample_rate / 50),
|
||||
buffer_size(frame_size * buffer_frames),
|
||||
buffer((unsigned char *)xalloc(buffer_size)),
|
||||
enc(_enc)
|
||||
{
|
||||
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
|
||||
|
||||
stream.Initialize(GenerateOggSerial());
|
||||
}
|
||||
|
||||
Encoder *
|
||||
PreparedOpusEncoder::Open(AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
/* libopus supports only 48 kHz */
|
||||
_audio_format.sample_rate = 48000;
|
||||
audio_format.sample_rate = 48000;
|
||||
|
||||
if (_audio_format.channels > 2)
|
||||
_audio_format.channels = 1;
|
||||
if (audio_format.channels > 2)
|
||||
audio_format.channels = 1;
|
||||
|
||||
switch (_audio_format.format) {
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S16:
|
||||
case SampleFormat::FLOAT:
|
||||
break;
|
||||
|
||||
case SampleFormat::S8:
|
||||
_audio_format.format = SampleFormat::S16;
|
||||
audio_format.format = SampleFormat::S16;
|
||||
break;
|
||||
|
||||
default:
|
||||
_audio_format.format = SampleFormat::FLOAT;
|
||||
audio_format.format = SampleFormat::FLOAT;
|
||||
break;
|
||||
}
|
||||
|
||||
audio_format = _audio_format;
|
||||
frame_size = _audio_format.GetFrameSize();
|
||||
|
||||
int error_code;
|
||||
enc = opus_encoder_create(_audio_format.sample_rate,
|
||||
_audio_format.channels,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
&error_code);
|
||||
auto *enc = opus_encoder_create(audio_format.sample_rate,
|
||||
audio_format.channels,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
&error_code);
|
||||
if (enc == nullptr) {
|
||||
error.Set(opus_encoder_domain, error_code,
|
||||
opus_strerror(error_code));
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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_GET_LOOKAHEAD(&lookahead));
|
||||
|
||||
buffer_frames = _audio_format.sample_rate / 50;
|
||||
buffer_size = frame_size * buffer_frames;
|
||||
buffer_position = 0;
|
||||
buffer = (unsigned char *)xalloc(buffer_size);
|
||||
|
||||
stream.Initialize(GenerateOggSerial());
|
||||
packetno = 0;
|
||||
|
||||
return true;
|
||||
return new OpusEncoder(audio_format, enc);
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_open(Encoder *_encoder,
|
||||
static Encoder *
|
||||
opus_encoder_open(PreparedEncoder *_encoder,
|
||||
AudioFormat &audio_format,
|
||||
Error &error)
|
||||
{
|
||||
auto &encoder = *(OpusEncoder *)_encoder;
|
||||
auto &encoder = *(PreparedOpusEncoder *)_encoder;
|
||||
return encoder.Open(audio_format, error);
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::Close()
|
||||
OpusEncoder::~OpusEncoder()
|
||||
{
|
||||
stream.Deinitialize();
|
||||
free(buffer);
|
||||
opus_encoder_destroy(enc);
|
||||
}
|
||||
|
||||
static void
|
||||
opus_encoder_close(Encoder *_encoder)
|
||||
{
|
||||
auto &encoder = *(OpusEncoder *)_encoder;
|
||||
encoder.Close();
|
||||
}
|
||||
|
||||
bool
|
||||
OpusEncoder::DoEncode(bool eos, Error &error)
|
||||
{
|
||||
@ -281,13 +280,6 @@ OpusEncoder::End(Error &error)
|
||||
return DoEncode(true, error);
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_end(Encoder *_encoder, Error &error)
|
||||
{
|
||||
auto &encoder = *(OpusEncoder *)_encoder;
|
||||
return encoder.End(error);
|
||||
}
|
||||
|
||||
bool
|
||||
OpusEncoder::Flush(gcc_unused Error &error)
|
||||
{
|
||||
@ -295,14 +287,6 @@ OpusEncoder::Flush(gcc_unused Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_flush(Encoder *_encoder, Error &error)
|
||||
{
|
||||
auto &encoder = *(OpusEncoder *)_encoder;
|
||||
|
||||
return encoder.Flush(error);
|
||||
}
|
||||
|
||||
bool
|
||||
OpusEncoder::WriteSilence(unsigned fill_frames, Error &error)
|
||||
{
|
||||
@ -360,15 +344,6 @@ OpusEncoder::Write(const void *_data, size_t length, Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
opus_encoder_write(Encoder *_encoder,
|
||||
const void *data, size_t length,
|
||||
Error &error)
|
||||
{
|
||||
auto &encoder = *(OpusEncoder *)_encoder;
|
||||
return encoder.Write(data, length, error);
|
||||
}
|
||||
|
||||
void
|
||||
OpusEncoder::GenerateHead()
|
||||
{
|
||||
@ -430,30 +405,18 @@ OpusEncoder::Read(void *dest, size_t length)
|
||||
return stream.PageOut(dest, length);
|
||||
}
|
||||
|
||||
static size_t
|
||||
opus_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
auto &encoder = *(OpusEncoder *)_encoder;
|
||||
return encoder.Read(dest, length);
|
||||
}
|
||||
|
||||
static const char *
|
||||
opus_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
||||
opus_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||
{
|
||||
return "audio/ogg";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const EncoderPlugin opus_encoder_plugin = {
|
||||
"opus",
|
||||
opus_encoder_init,
|
||||
opus_encoder_finish,
|
||||
opus_encoder_open,
|
||||
opus_encoder_close,
|
||||
opus_encoder_end,
|
||||
opus_encoder_flush,
|
||||
nullptr,
|
||||
nullptr,
|
||||
opus_encoder_write,
|
||||
opus_encoder_read,
|
||||
opus_encoder_get_mime_type,
|
||||
};
|
||||
|
@ -22,7 +22,6 @@
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "config/ConfigError.hxx"
|
||||
#include "util/Manual.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
@ -34,32 +33,71 @@ extern "C"
|
||||
static constexpr size_t BUFFER_INIT_SIZE = 8192;
|
||||
static constexpr unsigned CHANNELS = 2;
|
||||
|
||||
struct ShineEncoder {
|
||||
Encoder encoder;
|
||||
class ShineEncoder final : public Encoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
AudioFormat audio_format;
|
||||
const shine_t shine;
|
||||
|
||||
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<uint8_t> output_buffer;
|
||||
|
||||
public:
|
||||
ShineEncoder(AudioFormat _audio_format, shine_t _shine)
|
||||
: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]},
|
||||
output_buffer(BUFFER_INIT_SIZE) {}
|
||||
|
||||
~ShineEncoder() 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 */
|
||||
bool End(Error &error) override {
|
||||
return Flush(error);
|
||||
}
|
||||
|
||||
bool Flush(Error &) override;
|
||||
|
||||
bool Write(const void *data, size_t length, Error &) override;
|
||||
|
||||
size_t Read(void *dest, size_t length) override {
|
||||
return output_buffer.Read((uint8_t *)dest, length);
|
||||
}
|
||||
};
|
||||
|
||||
struct PreparedShineEncoder {
|
||||
PreparedEncoder encoder;
|
||||
|
||||
shine_config_t config;
|
||||
|
||||
size_t frame_size;
|
||||
size_t input_pos;
|
||||
int16_t *stereo[CHANNELS];
|
||||
|
||||
Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
|
||||
|
||||
ShineEncoder():encoder(shine_encoder_plugin){}
|
||||
PreparedShineEncoder():encoder(shine_encoder_plugin) {}
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
|
||||
bool Setup(Error &error);
|
||||
|
||||
bool WriteChunk(bool flush);
|
||||
};
|
||||
|
||||
inline bool
|
||||
ShineEncoder::Configure(const ConfigBlock &block, gcc_unused Error &error)
|
||||
PreparedShineEncoder::Configure(const ConfigBlock &block, Error &)
|
||||
{
|
||||
shine_set_config_mpeg_defaults(&config.mpeg);
|
||||
config.mpeg.bitr = block.GetBlockValue("bitrate", 128);
|
||||
@ -67,10 +105,10 @@ ShineEncoder::Configure(const ConfigBlock &block, gcc_unused Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
shine_encoder_init(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
ShineEncoder *encoder = new ShineEncoder();
|
||||
auto *encoder = new PreparedShineEncoder();
|
||||
|
||||
/* load configuration from "block" */
|
||||
if (!encoder->Configure(block, error)) {
|
||||
@ -83,16 +121,20 @@ shine_encoder_init(const ConfigBlock &block, Error &error)
|
||||
}
|
||||
|
||||
static void
|
||||
shine_encoder_finish(Encoder *_encoder)
|
||||
shine_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
ShineEncoder *encoder = (ShineEncoder *)_encoder;
|
||||
auto *encoder = (PreparedShineEncoder *)_encoder;
|
||||
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
inline bool
|
||||
ShineEncoder::Setup(Error &error)
|
||||
static shine_t
|
||||
SetupShine(shine_config_t config, AudioFormat &audio_format,
|
||||
Error &error)
|
||||
{
|
||||
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 =
|
||||
@ -106,61 +148,28 @@ ShineEncoder::Setup(Error &error)
|
||||
config.wave.samplerate,
|
||||
config.mpeg.bitr);
|
||||
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shine = shine_initialise(&config);
|
||||
|
||||
if (!shine) {
|
||||
auto shine = shine_initialise(&config);
|
||||
if (!shine)
|
||||
error.Format(config_domain,
|
||||
"error initializing shine.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
frame_size = shine_samples_per_pass(shine);
|
||||
|
||||
return true;
|
||||
return shine;
|
||||
}
|
||||
|
||||
static bool
|
||||
shine_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
static Encoder *
|
||||
shine_encoder_open(PreparedEncoder *_encoder, AudioFormat &audio_format,
|
||||
Error &error)
|
||||
{
|
||||
ShineEncoder *encoder = (ShineEncoder *)_encoder;
|
||||
auto *encoder = (PreparedShineEncoder *)_encoder;
|
||||
|
||||
audio_format.format = SampleFormat::S16;
|
||||
audio_format.channels = CHANNELS;
|
||||
encoder->audio_format = audio_format;
|
||||
auto shine = SetupShine(encoder->config, audio_format, error);
|
||||
if (!shine)
|
||||
return nullptr;
|
||||
|
||||
if (!encoder->Setup(error))
|
||||
return false;
|
||||
|
||||
encoder->stereo[0] = new int16_t[encoder->frame_size];
|
||||
encoder->stereo[1] = new int16_t[encoder->frame_size];
|
||||
/* workaround for bug:
|
||||
https://github.com/savonet/shine/issues/11 */
|
||||
encoder->input_pos = SHINE_MAX_SAMPLES + 1;
|
||||
|
||||
encoder->output_buffer.Construct(BUFFER_INIT_SIZE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
shine_encoder_close(Encoder *_encoder)
|
||||
{
|
||||
ShineEncoder *encoder = (ShineEncoder *)_encoder;
|
||||
|
||||
if (encoder->input_pos > SHINE_MAX_SAMPLES) {
|
||||
/* write zero chunk */
|
||||
encoder->input_pos = 0;
|
||||
encoder->WriteChunk(true);
|
||||
}
|
||||
|
||||
shine_close(encoder->shine);
|
||||
delete[] encoder->stereo[0];
|
||||
delete[] encoder->stereo[1];
|
||||
encoder->output_buffer.Destruct();
|
||||
return new ShineEncoder(audio_format, shine);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -179,7 +188,7 @@ ShineEncoder::WriteChunk(bool flush)
|
||||
shine_encode_buffer(shine, stereo, &written);
|
||||
|
||||
if (written > 0)
|
||||
output_buffer->Append(out, written);
|
||||
output_buffer.Append(out, written);
|
||||
|
||||
input_pos = 0;
|
||||
}
|
||||
@ -187,65 +196,50 @@ ShineEncoder::WriteChunk(bool flush)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
shine_encoder_write(Encoder *_encoder,
|
||||
const void *_data, size_t length,
|
||||
gcc_unused Error &error)
|
||||
bool
|
||||
ShineEncoder::Write(const void *_data, size_t length, gcc_unused Error &error)
|
||||
{
|
||||
ShineEncoder *encoder = (ShineEncoder *)_encoder;
|
||||
const int16_t *data = (const int16_t*)_data;
|
||||
length /= sizeof(*data) * encoder->audio_format.channels;
|
||||
length /= sizeof(*data) * audio_format.channels;
|
||||
size_t written = 0;
|
||||
|
||||
if (encoder->input_pos > SHINE_MAX_SAMPLES) {
|
||||
encoder->input_pos = 0;
|
||||
}
|
||||
if (input_pos > SHINE_MAX_SAMPLES)
|
||||
input_pos = 0;
|
||||
|
||||
/* write all data to de-interleaved buffers */
|
||||
while (written < length) {
|
||||
for (;
|
||||
written < length
|
||||
&& encoder->input_pos < encoder->frame_size;
|
||||
written++, encoder->input_pos++) {
|
||||
written < length && input_pos < frame_size;
|
||||
written++, input_pos++) {
|
||||
const size_t base =
|
||||
written * encoder->audio_format.channels;
|
||||
encoder->stereo[0][encoder->input_pos] = data[base];
|
||||
encoder->stereo[1][encoder->input_pos] = data[base + 1];
|
||||
written * audio_format.channels;
|
||||
stereo[0][input_pos] = data[base];
|
||||
stereo[1][input_pos] = data[base + 1];
|
||||
}
|
||||
/* write if chunk is filled */
|
||||
encoder->WriteChunk(false);
|
||||
WriteChunk(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
shine_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
|
||||
bool
|
||||
ShineEncoder::Flush(gcc_unused Error &error)
|
||||
{
|
||||
ShineEncoder *encoder = (ShineEncoder *)_encoder;
|
||||
|
||||
/* flush buffers and flush shine */
|
||||
encoder->WriteChunk(true);
|
||||
WriteChunk(true);
|
||||
|
||||
int written;
|
||||
const uint8_t *data = shine_flush(encoder->shine, &written);
|
||||
const uint8_t *data = shine_flush(shine, &written);
|
||||
|
||||
if (written > 0)
|
||||
encoder->output_buffer->Append(data, written);
|
||||
output_buffer.Append(data, written);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
shine_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
ShineEncoder *encoder = (ShineEncoder *)_encoder;
|
||||
|
||||
return encoder->output_buffer->Read((uint8_t *)dest, length);
|
||||
}
|
||||
|
||||
static const char *
|
||||
shine_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
||||
shine_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||
{
|
||||
return "audio/mpeg";
|
||||
}
|
||||
@ -255,12 +249,5 @@ const EncoderPlugin shine_encoder_plugin = {
|
||||
shine_encoder_init,
|
||||
shine_encoder_finish,
|
||||
shine_encoder_open,
|
||||
shine_encoder_close,
|
||||
shine_encoder_flush,
|
||||
shine_encoder_flush,
|
||||
nullptr,
|
||||
nullptr,
|
||||
shine_encoder_write,
|
||||
shine_encoder_read,
|
||||
shine_encoder_get_mime_type,
|
||||
};
|
||||
|
@ -32,26 +32,53 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
struct TwolameEncoder final {
|
||||
Encoder encoder;
|
||||
|
||||
AudioFormat audio_format;
|
||||
float quality;
|
||||
int bitrate;
|
||||
class TwolameEncoder final : public Encoder {
|
||||
const AudioFormat audio_format;
|
||||
|
||||
twolame_options *options;
|
||||
|
||||
unsigned char output_buffer[32768];
|
||||
size_t output_buffer_length;
|
||||
size_t output_buffer_position;
|
||||
size_t output_buffer_length = 0;
|
||||
size_t output_buffer_position = 0;
|
||||
|
||||
/**
|
||||
* Call libtwolame's flush function when the output_buffer is
|
||||
* empty?
|
||||
*/
|
||||
bool flush;
|
||||
bool flush = false;
|
||||
|
||||
TwolameEncoder():encoder(twolame_encoder_plugin) {}
|
||||
public:
|
||||
TwolameEncoder(const AudioFormat _audio_format,
|
||||
twolame_options *_options)
|
||||
:Encoder(false),
|
||||
audio_format(_audio_format), options(_options) {}
|
||||
~TwolameEncoder() override;
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
|
||||
bool End(Error &) override {
|
||||
flush = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Flush(Error &) override {
|
||||
flush = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Write(const void *data, size_t length, Error &) override;
|
||||
size_t Read(void *dest, size_t length) override;
|
||||
};
|
||||
|
||||
struct PreparedTwolameEncoder final {
|
||||
PreparedEncoder encoder;
|
||||
|
||||
float quality;
|
||||
int bitrate;
|
||||
|
||||
PreparedTwolameEncoder():encoder(twolame_encoder_plugin) {}
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
};
|
||||
@ -59,7 +86,7 @@ struct TwolameEncoder final {
|
||||
static constexpr Domain twolame_encoder_domain("twolame_encoder");
|
||||
|
||||
bool
|
||||
TwolameEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
PreparedTwolameEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
const char *value;
|
||||
char *endptr;
|
||||
@ -106,13 +133,13 @@ TwolameEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
twolame_encoder_init(const ConfigBlock &block, Error &error_r)
|
||||
{
|
||||
FormatDebug(twolame_encoder_domain,
|
||||
"libtwolame version %s", get_twolame_version());
|
||||
|
||||
TwolameEncoder *encoder = new TwolameEncoder();
|
||||
auto *encoder = new PreparedTwolameEncoder();
|
||||
|
||||
/* load configuration from "block" */
|
||||
if (!encoder->Configure(block, error_r)) {
|
||||
@ -125,9 +152,9 @@ twolame_encoder_init(const ConfigBlock &block, Error &error_r)
|
||||
}
|
||||
|
||||
static void
|
||||
twolame_encoder_finish(Encoder *_encoder)
|
||||
twolame_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
|
||||
auto *encoder = (PreparedTwolameEncoder *)_encoder;
|
||||
|
||||
/* the real libtwolame cleanup was already performed by
|
||||
twolame_encoder_close(), so no real work here */
|
||||
@ -135,17 +162,18 @@ twolame_encoder_finish(Encoder *_encoder)
|
||||
}
|
||||
|
||||
static bool
|
||||
twolame_encoder_setup(TwolameEncoder *encoder, Error &error)
|
||||
twolame_encoder_setup(twolame_options *options, float quality, int bitrate,
|
||||
const AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
if (encoder->quality >= -1.0) {
|
||||
if (quality >= -1.0) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
if (0 != twolame_set_VBR(encoder->options, true)) {
|
||||
if (0 != twolame_set_VBR(options, true)) {
|
||||
error.Set(twolame_encoder_domain,
|
||||
"error setting twolame VBR mode");
|
||||
return false;
|
||||
}
|
||||
if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
|
||||
if (0 != twolame_set_VBR_q(options, quality)) {
|
||||
error.Set(twolame_encoder_domain,
|
||||
"error setting twolame VBR quality");
|
||||
return false;
|
||||
@ -153,28 +181,27 @@ twolame_encoder_setup(TwolameEncoder *encoder, Error &error)
|
||||
} else {
|
||||
/* a bit rate was configured */
|
||||
|
||||
if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
|
||||
if (0 != twolame_set_brate(options, bitrate)) {
|
||||
error.Set(twolame_encoder_domain,
|
||||
"error setting twolame bitrate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 != twolame_set_num_channels(encoder->options,
|
||||
encoder->audio_format.channels)) {
|
||||
if (0 != twolame_set_num_channels(options, audio_format.channels)) {
|
||||
error.Set(twolame_encoder_domain,
|
||||
"error setting twolame num channels");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != twolame_set_in_samplerate(encoder->options,
|
||||
encoder->audio_format.sample_rate)) {
|
||||
if (0 != twolame_set_in_samplerate(options,
|
||||
audio_format.sample_rate)) {
|
||||
error.Set(twolame_encoder_domain,
|
||||
"error setting twolame sample rate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 > twolame_init_params(encoder->options)) {
|
||||
if (0 > twolame_init_params(options)) {
|
||||
error.Set(twolame_encoder_domain,
|
||||
"error initializing twolame params");
|
||||
return false;
|
||||
@ -183,117 +210,89 @@ twolame_encoder_setup(TwolameEncoder *encoder, Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
|
||||
static Encoder *
|
||||
twolame_encoder_open(PreparedEncoder *_encoder, AudioFormat &audio_format,
|
||||
Error &error)
|
||||
{
|
||||
TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
|
||||
auto *encoder = (PreparedTwolameEncoder *)_encoder;
|
||||
|
||||
audio_format.format = SampleFormat::S16;
|
||||
audio_format.channels = 2;
|
||||
|
||||
encoder->audio_format = audio_format;
|
||||
|
||||
encoder->options = twolame_init();
|
||||
if (encoder->options == nullptr) {
|
||||
auto options = twolame_init();
|
||||
if (options == nullptr) {
|
||||
error.Set(twolame_encoder_domain, "twolame_init() failed");
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!twolame_encoder_setup(encoder, error)) {
|
||||
twolame_close(&encoder->options);
|
||||
return false;
|
||||
if (!twolame_encoder_setup(options, encoder->quality, encoder->bitrate,
|
||||
audio_format, error)) {
|
||||
twolame_close(&options);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
encoder->output_buffer_length = 0;
|
||||
encoder->output_buffer_position = 0;
|
||||
encoder->flush = false;
|
||||
|
||||
return true;
|
||||
return new TwolameEncoder(audio_format, options);
|
||||
}
|
||||
|
||||
static void
|
||||
twolame_encoder_close(Encoder *_encoder)
|
||||
TwolameEncoder::~TwolameEncoder()
|
||||
{
|
||||
TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
|
||||
|
||||
twolame_close(&encoder->options);
|
||||
twolame_close(&options);
|
||||
}
|
||||
|
||||
static bool
|
||||
twolame_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
|
||||
{
|
||||
TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
|
||||
|
||||
encoder->flush = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
twolame_encoder_write(Encoder *_encoder,
|
||||
const void *data, size_t length,
|
||||
bool
|
||||
TwolameEncoder::Write(const void *data, size_t length,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
|
||||
const int16_t *src = (const int16_t*)data;
|
||||
|
||||
assert(encoder->output_buffer_position ==
|
||||
encoder->output_buffer_length);
|
||||
assert(output_buffer_position == output_buffer_length);
|
||||
|
||||
const unsigned num_frames =
|
||||
length / encoder->audio_format.GetFrameSize();
|
||||
const unsigned num_frames = length / audio_format.GetFrameSize();
|
||||
|
||||
int bytes_out = twolame_encode_buffer_interleaved(encoder->options,
|
||||
int bytes_out = twolame_encode_buffer_interleaved(options,
|
||||
src, num_frames,
|
||||
encoder->output_buffer,
|
||||
sizeof(encoder->output_buffer));
|
||||
output_buffer,
|
||||
sizeof(output_buffer));
|
||||
if (bytes_out < 0) {
|
||||
error.Set(twolame_encoder_domain, "twolame encoder failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder->output_buffer_length = (size_t)bytes_out;
|
||||
encoder->output_buffer_position = 0;
|
||||
output_buffer_length = (size_t)bytes_out;
|
||||
output_buffer_position = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
twolame_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
size_t
|
||||
TwolameEncoder::Read(void *dest, size_t length)
|
||||
{
|
||||
TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
|
||||
assert(output_buffer_position <= output_buffer_length);
|
||||
|
||||
assert(encoder->output_buffer_position <=
|
||||
encoder->output_buffer_length);
|
||||
|
||||
if (encoder->output_buffer_position == encoder->output_buffer_length &&
|
||||
encoder->flush) {
|
||||
int ret = twolame_encode_flush(encoder->options,
|
||||
encoder->output_buffer,
|
||||
sizeof(encoder->output_buffer));
|
||||
if (output_buffer_position == output_buffer_length && flush) {
|
||||
int ret = twolame_encode_flush(options, output_buffer,
|
||||
sizeof(output_buffer));
|
||||
if (ret > 0) {
|
||||
encoder->output_buffer_length = (size_t)ret;
|
||||
encoder->output_buffer_position = 0;
|
||||
output_buffer_length = (size_t)ret;
|
||||
output_buffer_position = 0;
|
||||
}
|
||||
|
||||
encoder->flush = false;
|
||||
flush = false;
|
||||
}
|
||||
|
||||
|
||||
const size_t remainning = encoder->output_buffer_length
|
||||
- encoder->output_buffer_position;
|
||||
const size_t remainning = output_buffer_length - output_buffer_position;
|
||||
if (length > remainning)
|
||||
length = remainning;
|
||||
|
||||
memcpy(dest, encoder->output_buffer + encoder->output_buffer_position,
|
||||
length);
|
||||
memcpy(dest, output_buffer + output_buffer_position, length);
|
||||
|
||||
encoder->output_buffer_position += length;
|
||||
output_buffer_position += length;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
static const char *
|
||||
twolame_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
||||
twolame_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||
{
|
||||
return "audio/mpeg";
|
||||
}
|
||||
@ -303,12 +302,5 @@ const EncoderPlugin twolame_encoder_plugin = {
|
||||
twolame_encoder_init,
|
||||
twolame_encoder_finish,
|
||||
twolame_encoder_open,
|
||||
twolame_encoder_close,
|
||||
twolame_encoder_flush,
|
||||
twolame_encoder_flush,
|
||||
nullptr,
|
||||
nullptr,
|
||||
twolame_encoder_write,
|
||||
twolame_encoder_read,
|
||||
twolame_encoder_get_mime_type,
|
||||
};
|
||||
|
@ -31,17 +31,7 @@
|
||||
|
||||
#include <vorbis/vorbisenc.h>
|
||||
|
||||
struct VorbisEncoder {
|
||||
/** the base class */
|
||||
Encoder encoder;
|
||||
|
||||
/* configuration */
|
||||
|
||||
float quality;
|
||||
int bitrate;
|
||||
|
||||
/* runtime information */
|
||||
|
||||
class VorbisEncoder final : public Encoder {
|
||||
AudioFormat audio_format;
|
||||
|
||||
vorbis_dsp_state vd;
|
||||
@ -50,22 +40,57 @@ struct VorbisEncoder {
|
||||
|
||||
OggStream stream;
|
||||
|
||||
VorbisEncoder():encoder(vorbis_encoder_plugin) {}
|
||||
public:
|
||||
VorbisEncoder()
|
||||
:Encoder(true) {}
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
virtual ~VorbisEncoder() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
bool Reinit(Error &error);
|
||||
bool Open(float quality, int bitrate, AudioFormat &audio_format,
|
||||
Error &error);
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
bool End(Error &error) override {
|
||||
return PreTag(error);
|
||||
}
|
||||
|
||||
bool Flush(Error &error) override;
|
||||
bool PreTag(Error &error) override;
|
||||
bool SendTag(const Tag &tag, Error &error) override;
|
||||
|
||||
bool Write(const void *data, size_t length, Error &) override;
|
||||
|
||||
size_t Read(void *dest, size_t length) override {
|
||||
return stream.PageOut(dest, length);
|
||||
}
|
||||
|
||||
private:
|
||||
void HeaderOut(vorbis_comment &vc);
|
||||
void SendHeader();
|
||||
void BlockOut();
|
||||
void Clear();
|
||||
};
|
||||
|
||||
struct PreparedVorbisEncoder {
|
||||
/** the base class */
|
||||
PreparedEncoder encoder;
|
||||
|
||||
/* configuration */
|
||||
|
||||
float quality;
|
||||
int bitrate;
|
||||
|
||||
PreparedVorbisEncoder():encoder(vorbis_encoder_plugin) {}
|
||||
|
||||
bool Configure(const ConfigBlock &block, Error &error);
|
||||
};
|
||||
|
||||
static constexpr Domain vorbis_encoder_domain("vorbis_encoder");
|
||||
|
||||
bool
|
||||
VorbisEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
PreparedVorbisEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
const char *value = block.GetBlockValue("quality");
|
||||
if (value != nullptr) {
|
||||
@ -111,10 +136,10 @@ VorbisEncoder::Configure(const ConfigBlock &block, Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
vorbis_encoder_init(const ConfigBlock &block, Error &error)
|
||||
{
|
||||
auto *encoder = new VorbisEncoder();
|
||||
auto *encoder = new PreparedVorbisEncoder();
|
||||
|
||||
/* load configuration from "block" */
|
||||
if (!encoder->Configure(block, error)) {
|
||||
@ -127,9 +152,9 @@ vorbis_encoder_init(const ConfigBlock &block, Error &error)
|
||||
}
|
||||
|
||||
static void
|
||||
vorbis_encoder_finish(Encoder *_encoder)
|
||||
vorbis_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
VorbisEncoder *encoder = (VorbisEncoder *)_encoder;
|
||||
auto *encoder = (PreparedVorbisEncoder *)_encoder;
|
||||
|
||||
/* the real libvorbis/libogg cleanup was already performed by
|
||||
vorbis_encoder_close(), so no real work here */
|
||||
@ -137,8 +162,12 @@ vorbis_encoder_finish(Encoder *_encoder)
|
||||
}
|
||||
|
||||
bool
|
||||
VorbisEncoder::Reinit(Error &error)
|
||||
VorbisEncoder::Open(float quality, int bitrate, AudioFormat &_audio_format,
|
||||
Error &error)
|
||||
{
|
||||
_audio_format.format = SampleFormat::FLOAT;
|
||||
audio_format = _audio_format;
|
||||
|
||||
vorbis_info_init(&vi);
|
||||
|
||||
if (quality >= -1.0) {
|
||||
@ -171,6 +200,8 @@ VorbisEncoder::Reinit(Error &error)
|
||||
vorbis_block_init(&vd, &vb);
|
||||
stream.Initialize(GenerateOggSerial());
|
||||
|
||||
SendHeader();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -197,23 +228,20 @@ VorbisEncoder::SendHeader()
|
||||
vorbis_comment_clear(&vc);
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_encoder_open(Encoder *_encoder,
|
||||
static Encoder *
|
||||
vorbis_encoder_open(PreparedEncoder *_encoder,
|
||||
AudioFormat &audio_format,
|
||||
Error &error)
|
||||
{
|
||||
auto &encoder = *(VorbisEncoder *)_encoder;
|
||||
auto &encoder = *(PreparedVorbisEncoder *)_encoder;
|
||||
|
||||
audio_format.format = SampleFormat::FLOAT;
|
||||
auto *e = new VorbisEncoder();
|
||||
if (!e->Open(encoder.quality, encoder.bitrate, audio_format, error)) {
|
||||
delete e;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
encoder.audio_format = audio_format;
|
||||
|
||||
if (!encoder.Reinit(error))
|
||||
return false;
|
||||
|
||||
encoder.SendHeader();
|
||||
|
||||
return true;
|
||||
return e;
|
||||
}
|
||||
|
||||
void
|
||||
@ -225,14 +253,6 @@ VorbisEncoder::Clear()
|
||||
vorbis_info_clear(&vi);
|
||||
}
|
||||
|
||||
static void
|
||||
vorbis_encoder_close(Encoder *_encoder)
|
||||
{
|
||||
auto &encoder = *(VorbisEncoder *)_encoder;
|
||||
|
||||
encoder.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
VorbisEncoder::BlockOut()
|
||||
{
|
||||
@ -246,31 +266,27 @@ VorbisEncoder::BlockOut()
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
|
||||
bool
|
||||
VorbisEncoder::Flush(gcc_unused Error &error)
|
||||
{
|
||||
auto &encoder = *(VorbisEncoder *)_encoder;
|
||||
|
||||
encoder.stream.Flush();
|
||||
stream.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error)
|
||||
bool
|
||||
VorbisEncoder::PreTag(gcc_unused Error &error)
|
||||
{
|
||||
auto &encoder = *(VorbisEncoder *)_encoder;
|
||||
|
||||
vorbis_analysis_wrote(&encoder.vd, 0);
|
||||
encoder.BlockOut();
|
||||
vorbis_analysis_wrote(&vd, 0);
|
||||
BlockOut();
|
||||
|
||||
/* reinitialize vorbis_dsp_state and vorbis_block to reset the
|
||||
end-of-stream marker */
|
||||
vorbis_block_clear(&encoder.vb);
|
||||
vorbis_dsp_clear(&encoder.vd);
|
||||
vorbis_analysis_init(&encoder.vd, &encoder.vi);
|
||||
vorbis_block_init(&encoder.vd, &encoder.vb);
|
||||
vorbis_block_clear(&vb);
|
||||
vorbis_dsp_clear(&vd);
|
||||
vorbis_analysis_init(&vd, &vi);
|
||||
vorbis_block_init(&vd, &vb);
|
||||
|
||||
encoder.stream.Flush();
|
||||
stream.Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -284,11 +300,9 @@ copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag &tag)
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_encoder_tag(Encoder *_encoder, const Tag &tag,
|
||||
gcc_unused Error &error)
|
||||
bool
|
||||
VorbisEncoder::SendTag(const Tag &tag, gcc_unused Error &error)
|
||||
{
|
||||
auto &encoder = *(VorbisEncoder *)_encoder;
|
||||
vorbis_comment comment;
|
||||
|
||||
/* write the vorbis_comment object */
|
||||
@ -298,11 +312,11 @@ vorbis_encoder_tag(Encoder *_encoder, const Tag &tag,
|
||||
|
||||
/* reset ogg_stream_state and begin a new stream */
|
||||
|
||||
encoder.stream.Reinitialize(GenerateOggSerial());
|
||||
stream.Reinitialize(GenerateOggSerial());
|
||||
|
||||
/* send that vorbis_comment to the ogg_stream_state */
|
||||
|
||||
encoder.HeaderOut(comment);
|
||||
HeaderOut(comment);
|
||||
vorbis_comment_clear(&comment);
|
||||
|
||||
return true;
|
||||
@ -317,38 +331,25 @@ interleaved_to_vorbis_buffer(float **dest, const float *src,
|
||||
dest[j][i] = *src++;
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_encoder_write(Encoder *_encoder,
|
||||
const void *data, size_t length,
|
||||
gcc_unused Error &error)
|
||||
bool
|
||||
VorbisEncoder::Write(const void *data, size_t length, gcc_unused Error &error)
|
||||
{
|
||||
auto &encoder = *(VorbisEncoder *)_encoder;
|
||||
|
||||
unsigned num_frames = length / encoder.audio_format.GetFrameSize();
|
||||
unsigned num_frames = length / audio_format.GetFrameSize();
|
||||
|
||||
/* this is for only 16-bit audio */
|
||||
|
||||
interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder.vd,
|
||||
num_frames),
|
||||
interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&vd, num_frames),
|
||||
(const float *)data,
|
||||
num_frames,
|
||||
encoder.audio_format.channels);
|
||||
audio_format.channels);
|
||||
|
||||
vorbis_analysis_wrote(&encoder.vd, num_frames);
|
||||
encoder.BlockOut();
|
||||
vorbis_analysis_wrote(&vd, num_frames);
|
||||
BlockOut();
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
auto &encoder = *(VorbisEncoder *)_encoder;
|
||||
|
||||
return encoder.stream.PageOut(dest, length);
|
||||
}
|
||||
|
||||
static const char *
|
||||
vorbis_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
||||
vorbis_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||
{
|
||||
return "audio/ogg";
|
||||
}
|
||||
@ -358,12 +359,5 @@ const EncoderPlugin vorbis_encoder_plugin = {
|
||||
vorbis_encoder_init,
|
||||
vorbis_encoder_finish,
|
||||
vorbis_encoder_open,
|
||||
vorbis_encoder_close,
|
||||
vorbis_encoder_pre_tag,
|
||||
vorbis_encoder_flush,
|
||||
vorbis_encoder_pre_tag,
|
||||
vorbis_encoder_tag,
|
||||
vorbis_encoder_write,
|
||||
vorbis_encoder_read,
|
||||
vorbis_encoder_get_mime_type,
|
||||
};
|
||||
|
@ -21,7 +21,6 @@
|
||||
#include "WaveEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/Manual.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
@ -29,13 +28,26 @@
|
||||
|
||||
static constexpr uint16_t WAVE_FORMAT_PCM = 1;
|
||||
|
||||
struct WaveEncoder {
|
||||
Encoder encoder;
|
||||
class WaveEncoder final : public Encoder {
|
||||
unsigned bits;
|
||||
|
||||
Manual<DynamicFifoBuffer<uint8_t>> buffer;
|
||||
DynamicFifoBuffer<uint8_t> buffer;
|
||||
|
||||
WaveEncoder():encoder(wave_encoder_plugin) {}
|
||||
public:
|
||||
WaveEncoder(AudioFormat &audio_format);
|
||||
|
||||
/* virtual methods from class Encoder */
|
||||
bool Write(const void *data, size_t length, Error &) override;
|
||||
|
||||
size_t Read(void *dest, size_t length) override {
|
||||
return buffer.Read((uint8_t *)dest, length);
|
||||
}
|
||||
};
|
||||
|
||||
struct PreparedWaveEncoder {
|
||||
PreparedEncoder encoder;
|
||||
|
||||
PreparedWaveEncoder():encoder(wave_encoder_plugin) {}
|
||||
};
|
||||
|
||||
struct WaveHeader {
|
||||
@ -80,78 +92,71 @@ fill_wave_header(WaveHeader *header, int channels, int bits,
|
||||
header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));
|
||||
}
|
||||
|
||||
static Encoder *
|
||||
static PreparedEncoder *
|
||||
wave_encoder_init(gcc_unused const ConfigBlock &block,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
WaveEncoder *encoder = new WaveEncoder();
|
||||
auto *encoder = new PreparedWaveEncoder();
|
||||
return &encoder->encoder;
|
||||
}
|
||||
|
||||
static void
|
||||
wave_encoder_finish(Encoder *_encoder)
|
||||
wave_encoder_finish(PreparedEncoder *_encoder)
|
||||
{
|
||||
WaveEncoder *encoder = (WaveEncoder *)_encoder;
|
||||
auto *encoder = (PreparedWaveEncoder *)_encoder;
|
||||
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
static bool
|
||||
wave_encoder_open(Encoder *_encoder,
|
||||
AudioFormat &audio_format,
|
||||
gcc_unused Error &error)
|
||||
WaveEncoder::WaveEncoder(AudioFormat &audio_format)
|
||||
:Encoder(false),
|
||||
buffer(8192)
|
||||
{
|
||||
WaveEncoder *encoder = (WaveEncoder *)_encoder;
|
||||
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S8:
|
||||
encoder->bits = 8;
|
||||
bits = 8;
|
||||
break;
|
||||
|
||||
case SampleFormat::S16:
|
||||
encoder->bits = 16;
|
||||
bits = 16;
|
||||
break;
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
encoder->bits = 24;
|
||||
bits = 24;
|
||||
break;
|
||||
|
||||
case SampleFormat::S32:
|
||||
encoder->bits = 32;
|
||||
bits = 32;
|
||||
break;
|
||||
|
||||
default:
|
||||
audio_format.format = SampleFormat::S16;
|
||||
encoder->bits = 16;
|
||||
bits = 16;
|
||||
break;
|
||||
}
|
||||
|
||||
encoder->buffer.Construct(8192);
|
||||
|
||||
auto range = encoder->buffer->Write();
|
||||
auto range = buffer.Write();
|
||||
assert(range.size >= sizeof(WaveHeader));
|
||||
auto *header = (WaveHeader *)range.data;
|
||||
|
||||
/* create PCM wave header in initial buffer */
|
||||
fill_wave_header(header,
|
||||
audio_format.channels,
|
||||
encoder->bits,
|
||||
bits,
|
||||
audio_format.sample_rate,
|
||||
(encoder->bits / 8) * audio_format.channels);
|
||||
(bits / 8) * audio_format.channels);
|
||||
|
||||
encoder->buffer->Append(sizeof(*header));
|
||||
|
||||
return true;
|
||||
buffer.Append(sizeof(*header));
|
||||
}
|
||||
|
||||
static void
|
||||
wave_encoder_close(Encoder *_encoder)
|
||||
static Encoder *
|
||||
wave_encoder_open(gcc_unused PreparedEncoder *_encoder,
|
||||
AudioFormat &audio_format,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
WaveEncoder *encoder = (WaveEncoder *)_encoder;
|
||||
|
||||
encoder->buffer.Destruct();
|
||||
return new WaveEncoder(audio_format);
|
||||
}
|
||||
|
||||
static size_t
|
||||
@ -194,17 +199,14 @@ pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length)
|
||||
return (dst8 - dst_old);
|
||||
}
|
||||
|
||||
static bool
|
||||
wave_encoder_write(Encoder *_encoder,
|
||||
const void *src, size_t length,
|
||||
bool
|
||||
WaveEncoder::Write(const void *src, size_t length,
|
||||
gcc_unused Error &error)
|
||||
{
|
||||
WaveEncoder *encoder = (WaveEncoder *)_encoder;
|
||||
|
||||
uint8_t *dst = encoder->buffer->Write(length);
|
||||
uint8_t *dst = buffer.Write(length);
|
||||
|
||||
if (IsLittleEndian()) {
|
||||
switch (encoder->bits) {
|
||||
switch (bits) {
|
||||
case 8:
|
||||
case 16:
|
||||
case 32:// optimized cases
|
||||
@ -215,7 +217,7 @@ wave_encoder_write(Encoder *_encoder,
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (encoder->bits) {
|
||||
switch (bits) {
|
||||
case 8:
|
||||
memcpy(dst, src, length);
|
||||
break;
|
||||
@ -233,20 +235,12 @@ wave_encoder_write(Encoder *_encoder,
|
||||
}
|
||||
}
|
||||
|
||||
encoder->buffer->Append(length);
|
||||
buffer.Append(length);
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
wave_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
WaveEncoder *encoder = (WaveEncoder *)_encoder;
|
||||
|
||||
return encoder->buffer->Read((uint8_t *)dest, length);
|
||||
}
|
||||
|
||||
static const char *
|
||||
wave_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
||||
wave_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||
{
|
||||
return "audio/wav";
|
||||
}
|
||||
@ -256,12 +250,5 @@ const EncoderPlugin wave_encoder_plugin = {
|
||||
wave_encoder_init,
|
||||
wave_encoder_finish,
|
||||
wave_encoder_open,
|
||||
wave_encoder_close,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
wave_encoder_write,
|
||||
wave_encoder_read,
|
||||
wave_encoder_get_mime_type,
|
||||
};
|
||||
|
@ -47,7 +47,8 @@ class RecorderOutput {
|
||||
/**
|
||||
* The configured encoder plugin.
|
||||
*/
|
||||
Encoder *encoder = nullptr;
|
||||
PreparedEncoder *prepared_encoder = nullptr;
|
||||
Encoder *encoder;
|
||||
|
||||
/**
|
||||
* The destination file name.
|
||||
@ -75,8 +76,8 @@ class RecorderOutput {
|
||||
:base(recorder_output_plugin) {}
|
||||
|
||||
~RecorderOutput() {
|
||||
if (encoder != nullptr)
|
||||
encoder->Dispose();
|
||||
if (prepared_encoder != nullptr)
|
||||
prepared_encoder->Dispose();
|
||||
}
|
||||
|
||||
bool Initialize(const ConfigBlock &block, Error &error_r) {
|
||||
@ -148,8 +149,8 @@ RecorderOutput::Configure(const ConfigBlock &block, Error &error)
|
||||
|
||||
/* initialize encoder */
|
||||
|
||||
encoder = encoder_init(*encoder_plugin, block, error);
|
||||
if (encoder == nullptr)
|
||||
prepared_encoder = encoder_init(*encoder_plugin, block, error);
|
||||
if (prepared_encoder == nullptr)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@ -205,7 +206,8 @@ RecorderOutput::Open(AudioFormat &audio_format, Error &error)
|
||||
|
||||
/* open the encoder */
|
||||
|
||||
if (!encoder->Open(audio_format, error)) {
|
||||
encoder = prepared_encoder->Open(audio_format, error);
|
||||
if (encoder == nullptr) {
|
||||
delete file;
|
||||
return false;
|
||||
}
|
||||
@ -214,7 +216,7 @@ RecorderOutput::Open(AudioFormat &audio_format, Error &error)
|
||||
try {
|
||||
EncoderToFile();
|
||||
} catch (const std::exception &e) {
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
error.Set(recorder_domain, e.what());
|
||||
return false;
|
||||
}
|
||||
@ -224,7 +226,7 @@ RecorderOutput::Open(AudioFormat &audio_format, Error &error)
|
||||
|
||||
/* close the encoder for now; it will be opened as
|
||||
soon as we have received a tag */
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -237,19 +239,19 @@ RecorderOutput::Commit(Error &error)
|
||||
|
||||
/* flush the encoder and write the rest to the file */
|
||||
|
||||
bool success = encoder_end(encoder, error);
|
||||
bool success = encoder->End(error);
|
||||
if (success) {
|
||||
try {
|
||||
EncoderToFile();
|
||||
} catch (...) {
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/* now really close everything */
|
||||
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
|
||||
if (success) {
|
||||
try {
|
||||
@ -326,7 +328,8 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path, Error &error)
|
||||
}
|
||||
|
||||
AudioFormat new_audio_format = effective_audio_format;
|
||||
if (!encoder->Open(new_audio_format, error)) {
|
||||
encoder = prepared_encoder->Open(new_audio_format, error);
|
||||
if (encoder == nullptr) {
|
||||
delete new_file;
|
||||
return false;
|
||||
}
|
||||
@ -338,7 +341,7 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path, Error &error)
|
||||
try {
|
||||
EncoderToOutputStream(*new_file, *encoder);
|
||||
} catch (const std::exception &e) {
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
delete new_file;
|
||||
error.Set(recorder_domain, e.what());
|
||||
return false;
|
||||
@ -386,7 +389,7 @@ RecorderOutput::SendTag(const Tag &tag)
|
||||
}
|
||||
|
||||
Error error;
|
||||
if (!encoder_pre_tag(encoder, error)) {
|
||||
if (!encoder->PreTag(error)) {
|
||||
LogError(error);
|
||||
return;
|
||||
}
|
||||
@ -398,7 +401,7 @@ RecorderOutput::SendTag(const Tag &tag)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!encoder_tag(encoder, tag, error))
|
||||
if (!encoder->SendTag(tag, error))
|
||||
LogError(error);
|
||||
}
|
||||
|
||||
@ -413,7 +416,7 @@ RecorderOutput::Play(const void *chunk, size_t size, Error &error)
|
||||
return size;
|
||||
}
|
||||
|
||||
if (!encoder_write(encoder, chunk, size, error))
|
||||
if (!encoder->Write(chunk, size, error))
|
||||
return 0;
|
||||
|
||||
try {
|
||||
|
@ -45,7 +45,8 @@ struct ShoutOutput final {
|
||||
shout_t *shout_conn;
|
||||
shout_metadata_t *shout_meta;
|
||||
|
||||
Encoder *encoder = nullptr;
|
||||
PreparedEncoder *prepared_encoder = nullptr;
|
||||
Encoder *encoder;
|
||||
|
||||
float quality = -2.0;
|
||||
int bitrate = -1;
|
||||
@ -93,8 +94,7 @@ ShoutOutput::~ShoutOutput()
|
||||
if (shout_init_count == 0)
|
||||
shout_shutdown();
|
||||
|
||||
if (encoder != nullptr)
|
||||
encoder->Dispose();
|
||||
delete prepared_encoder;
|
||||
}
|
||||
|
||||
static const EncoderPlugin *
|
||||
@ -192,8 +192,8 @@ ShoutOutput::Configure(const ConfigBlock &block, Error &error)
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder = encoder_init(*encoder_plugin, block, error);
|
||||
if (encoder == nullptr)
|
||||
prepared_encoder = encoder_init(*encoder_plugin, block, error);
|
||||
if (prepared_encoder == nullptr)
|
||||
return false;
|
||||
|
||||
unsigned shout_format;
|
||||
@ -345,8 +345,8 @@ write_page(ShoutOutput *sd, Error &error)
|
||||
assert(sd->encoder != nullptr);
|
||||
|
||||
while (true) {
|
||||
size_t nbytes = encoder_read(sd->encoder,
|
||||
sd->buffer, sizeof(sd->buffer));
|
||||
size_t nbytes = sd->encoder->Read(sd->buffer,
|
||||
sizeof(sd->buffer));
|
||||
if (nbytes == 0)
|
||||
return true;
|
||||
|
||||
@ -362,10 +362,10 @@ void
|
||||
ShoutOutput::Close()
|
||||
{
|
||||
if (encoder != nullptr) {
|
||||
if (encoder_end(encoder, IgnoreError()))
|
||||
if (encoder->End(IgnoreError()))
|
||||
write_page(this, IgnoreError());
|
||||
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
if (shout_get_connected(shout_conn) != SHOUTERR_UNCONNECTED &&
|
||||
@ -406,13 +406,14 @@ ShoutOutput::Open(AudioFormat &audio_format, Error &error)
|
||||
if (!shout_connect(this, error))
|
||||
return false;
|
||||
|
||||
if (!encoder->Open(audio_format, error)) {
|
||||
encoder = prepared_encoder->Open(audio_format, error);
|
||||
if (encoder == nullptr) {
|
||||
shout_close(shout_conn);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!write_page(this, error)) {
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
shout_close(shout_conn);
|
||||
return false;
|
||||
}
|
||||
@ -433,7 +434,7 @@ ShoutOutput::Delay() const
|
||||
size_t
|
||||
ShoutOutput::Play(const void *chunk, size_t size, Error &error)
|
||||
{
|
||||
return encoder_write(encoder, chunk, size, error) &&
|
||||
return encoder->Write(chunk, size, error) &&
|
||||
write_page(this, error)
|
||||
? size
|
||||
: 0;
|
||||
@ -476,13 +477,13 @@ shout_tag_to_metadata(const Tag &tag, char *dest, size_t size)
|
||||
void
|
||||
ShoutOutput::SendTag(const Tag &tag)
|
||||
{
|
||||
if (encoder->plugin.tag != nullptr) {
|
||||
if (encoder->ImplementsTag()) {
|
||||
/* encoder plugin supports stream tags */
|
||||
|
||||
Error error;
|
||||
if (!encoder_pre_tag(encoder, error) ||
|
||||
if (!encoder->PreTag(error) ||
|
||||
!write_page(this, error) ||
|
||||
!encoder_tag(encoder, tag, error)) {
|
||||
!encoder->SendTag(tag, error)) {
|
||||
LogError(error);
|
||||
return;
|
||||
}
|
||||
|
@ -45,7 +45,8 @@ class EventLoop;
|
||||
class ServerSocket;
|
||||
class HttpdClient;
|
||||
class Page;
|
||||
struct Encoder;
|
||||
struct PreparedEncoder;
|
||||
class Encoder;
|
||||
struct Tag;
|
||||
|
||||
class HttpdOutput final : ServerSocket, DeferredMonitor {
|
||||
@ -60,6 +61,7 @@ class HttpdOutput final : ServerSocket, DeferredMonitor {
|
||||
/**
|
||||
* The configured encoder plugin.
|
||||
*/
|
||||
PreparedEncoder *prepared_encoder = nullptr;
|
||||
Encoder *encoder;
|
||||
|
||||
/**
|
||||
|
@ -63,8 +63,8 @@ HttpdOutput::~HttpdOutput()
|
||||
if (metadata != nullptr)
|
||||
metadata->Unref();
|
||||
|
||||
if (encoder != nullptr)
|
||||
encoder->Dispose();
|
||||
if (prepared_encoder != nullptr)
|
||||
prepared_encoder->Dispose();
|
||||
|
||||
}
|
||||
|
||||
@ -123,12 +123,12 @@ HttpdOutput::Configure(const ConfigBlock &block, Error &error)
|
||||
|
||||
/* initialize encoder */
|
||||
|
||||
encoder = encoder_init(*encoder_plugin, block, error);
|
||||
if (encoder == nullptr)
|
||||
prepared_encoder = encoder_init(*encoder_plugin, block, error);
|
||||
if (prepared_encoder == nullptr)
|
||||
return false;
|
||||
|
||||
/* determine content type */
|
||||
content_type = encoder_get_mime_type(encoder);
|
||||
content_type = encoder_get_mime_type(prepared_encoder);
|
||||
if (content_type == nullptr)
|
||||
content_type = "application/octet-stream";
|
||||
|
||||
@ -169,7 +169,7 @@ inline void
|
||||
HttpdOutput::AddClient(int fd)
|
||||
{
|
||||
auto *client = new HttpdClient(*this, fd, GetEventLoop(),
|
||||
encoder->plugin.tag == nullptr);
|
||||
!encoder->ImplementsTag());
|
||||
clients.push_front(*client);
|
||||
|
||||
/* pass metadata to client */
|
||||
@ -250,15 +250,14 @@ HttpdOutput::ReadPage()
|
||||
/* we have fed a lot of input into the encoder, but it
|
||||
didn't give anything back yet - flush now to avoid
|
||||
buffer underruns */
|
||||
encoder_flush(encoder, IgnoreError());
|
||||
encoder->Flush(IgnoreError());
|
||||
unflushed_input = 0;
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
do {
|
||||
size_t nbytes = encoder_read(encoder,
|
||||
buffer + size,
|
||||
sizeof(buffer) - size);
|
||||
size_t nbytes = encoder->Read(buffer + size,
|
||||
sizeof(buffer) - size);
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
@ -292,7 +291,8 @@ httpd_output_disable(AudioOutput *ao)
|
||||
inline bool
|
||||
HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
if (!encoder->Open(audio_format, error))
|
||||
encoder = prepared_encoder->Open(audio_format, error);
|
||||
if (encoder == nullptr)
|
||||
return false;
|
||||
|
||||
/* we have to remember the encoder header, i.e. the first
|
||||
@ -351,7 +351,7 @@ HttpdOutput::Close()
|
||||
if (header != nullptr)
|
||||
header->Unref();
|
||||
|
||||
encoder->Close();
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -441,7 +441,7 @@ HttpdOutput::BroadcastFromEncoder()
|
||||
inline bool
|
||||
HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
|
||||
{
|
||||
if (!encoder_write(encoder, chunk, size, error))
|
||||
if (!encoder->Write(chunk, size, error))
|
||||
return false;
|
||||
|
||||
unflushed_input += size;
|
||||
@ -491,18 +491,18 @@ httpd_output_pause(AudioOutput *ao)
|
||||
inline void
|
||||
HttpdOutput::SendTag(const Tag &tag)
|
||||
{
|
||||
if (encoder->plugin.tag != nullptr) {
|
||||
if (encoder->ImplementsTag()) {
|
||||
/* embed encoder tags */
|
||||
|
||||
/* flush the current stream, and end it */
|
||||
|
||||
encoder_pre_tag(encoder, IgnoreError());
|
||||
encoder->PreTag(IgnoreError());
|
||||
BroadcastFromEncoder();
|
||||
|
||||
/* send the tag to the encoder - which starts a new
|
||||
stream now */
|
||||
|
||||
encoder_tag(encoder, tag, IgnoreError());
|
||||
encoder->SendTag(tag, IgnoreError());
|
||||
|
||||
/* the first page generated by the encoder will now be
|
||||
used as the new "header" page, which is sent to all
|
||||
|
@ -65,8 +65,8 @@ int main(int argc, char **argv)
|
||||
|
||||
try {
|
||||
Error error;
|
||||
const auto encoder = encoder_init(*plugin, block, error);
|
||||
if (encoder == NULL) {
|
||||
const auto p_encoder = encoder_init(*plugin, block, error);
|
||||
if (p_encoder == nullptr) {
|
||||
LogError(error, "Failed to initialize encoder");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@ -81,7 +81,8 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (!encoder->Open(audio_format, error)) {
|
||||
auto *encoder = p_encoder->Open(audio_format, error);
|
||||
if (encoder == nullptr) {
|
||||
LogError(error, "Failed to open encoder");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@ -94,7 +95,7 @@ int main(int argc, char **argv)
|
||||
|
||||
ssize_t nbytes;
|
||||
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
|
||||
if (!encoder_write(encoder, buffer, nbytes, error)) {
|
||||
if (!encoder->Write(buffer, nbytes, error)) {
|
||||
LogError(error, "encoder_write() failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@ -102,15 +103,15 @@ int main(int argc, char **argv)
|
||||
EncoderToOutputStream(os, *encoder);
|
||||
}
|
||||
|
||||
if (!encoder_end(encoder, error)) {
|
||||
if (!encoder->End(error)) {
|
||||
LogError(error, "encoder_flush() failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
EncoderToOutputStream(os, *encoder);
|
||||
|
||||
encoder->Close();
|
||||
encoder->Dispose();
|
||||
delete encoder;
|
||||
p_encoder->Dispose();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
} catch (const std::exception &e) {
|
||||
|
@ -48,15 +48,15 @@ main(gcc_unused int argc, gcc_unused char **argv)
|
||||
ConfigBlock block;
|
||||
block.AddBlockParam("quality", "5.0", -1);
|
||||
|
||||
const auto encoder = encoder_init(*plugin, block, IgnoreError());
|
||||
assert(encoder != NULL);
|
||||
const auto p_encoder = encoder_init(*plugin, block, IgnoreError());
|
||||
assert(p_encoder != nullptr);
|
||||
|
||||
try {
|
||||
/* open the encoder */
|
||||
|
||||
AudioFormat audio_format(44100, SampleFormat::S16, 2);
|
||||
success = encoder->Open(audio_format, IgnoreError());
|
||||
assert(success);
|
||||
auto encoder = p_encoder->Open(audio_format, IgnoreError());
|
||||
assert(encoder != nullptr);
|
||||
|
||||
StdioOutputStream os(stdout);
|
||||
|
||||
@ -64,14 +64,14 @@ main(gcc_unused int argc, gcc_unused char **argv)
|
||||
|
||||
/* write a block of data */
|
||||
|
||||
success = encoder_write(encoder, zero, sizeof(zero), IgnoreError());
|
||||
success = encoder->Write(zero, sizeof(zero), IgnoreError());
|
||||
assert(success);
|
||||
|
||||
EncoderToOutputStream(os, *encoder);
|
||||
|
||||
/* write a tag */
|
||||
|
||||
success = encoder_pre_tag(encoder, IgnoreError());
|
||||
success = encoder->PreTag(IgnoreError());
|
||||
assert(success);
|
||||
|
||||
EncoderToOutputStream(os, *encoder);
|
||||
@ -85,25 +85,25 @@ main(gcc_unused int argc, gcc_unused char **argv)
|
||||
tag_builder.Commit(tag);
|
||||
}
|
||||
|
||||
success = encoder_tag(encoder, tag, IgnoreError());
|
||||
success = encoder->SendTag(tag, IgnoreError());
|
||||
assert(success);
|
||||
|
||||
EncoderToOutputStream(os, *encoder);
|
||||
|
||||
/* write another block of data */
|
||||
|
||||
success = encoder_write(encoder, zero, sizeof(zero), IgnoreError());
|
||||
success = encoder->Write(zero, sizeof(zero), IgnoreError());
|
||||
assert(success);
|
||||
|
||||
/* finish */
|
||||
|
||||
success = encoder_end(encoder, IgnoreError());
|
||||
success = encoder->End(IgnoreError());
|
||||
assert(success);
|
||||
|
||||
EncoderToOutputStream(os, *encoder);
|
||||
|
||||
encoder->Close();
|
||||
encoder->Dispose();
|
||||
delete encoder;
|
||||
p_encoder->Dispose();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
} catch (const std::exception &e) {
|
||||
|
Loading…
Reference in New Issue
Block a user