Commit e7edc026 authored by Max Kellermann's avatar Max Kellermann

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