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 class Encoder {
bool open, pre_tag, tag, end; const bool implements_tag;
#endif
explicit Encoder(const EncoderPlugin &_plugin) public:
:plugin(_plugin) explicit Encoder(bool _implements_tag)
#ifndef NDEBUG :implements_tag(_implements_tag) {}
, open(false) virtual ~Encoder() {}
#endif
{}
bool ImplementsTag() const {
return implements_tag;
}
/** /**
* Frees an #Encoder object. * Ends the stream: flushes the encoder object, generate an
* end-of-stream marker (if applicable), make everything which
* might currently be buffered available by encoder_read().
*
* After this function has been called, the encoder may not be
* usable for more data, and only Read() and Close() can be
* called.
*
* @return true on success
*/ */
void Dispose() { virtual bool End(gcc_unused Error &error) {
assert(!open); return true;
plugin.finish(this);
} }
/** /**
* Opens the object. You must call this prior to using it. * Flushes an encoder object, make everything which might
* Before you free it, you must call Close(). You may open * currently be buffered available by Read().
* 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 * @return true on success
*/ */
bool Open(AudioFormat &audio_format, Error &error) { virtual bool Flush(gcc_unused Error &error) {
assert(!open); return true;
bool success = plugin.open(this, audio_format, error);
#ifndef NDEBUG
open = success;
pre_tag = tag = end = false;
#endif
return success;
} }
/** /**
* Closes the object. This disables the encoder, and readies * Prepare for sending a tag to the encoder. This is used by
* it for reusal by calling Open() again. * some encoders to flush the previous sub-stream, in
* preparation to begin a new one.
*
* @return true on success
*/ */
void Close() { virtual bool PreTag(gcc_unused Error &error) {
assert(open); return true;
if (plugin.close != nullptr)
plugin.close(this);
#ifndef NDEBUG
open = false;
#endif
} }
};
/** /**
* Ends the stream: flushes the encoder object, generate an * Sends a tag to the encoder.
* end-of-stream marker (if applicable), make everything which might *
* currently be buffered available by encoder_read(). * Instructions: call PreTag(); then obtain flushed data with
* * Read(); finally call Tag().
* After this function has been called, the encoder may not be usable *
* for more data, and only encoder_read() and Encoder::Close() can be * @param tag the tag object
* called. * @return true on success
* */
* @param encoder the encoder virtual bool SendTag(gcc_unused const Tag &tag,
* @return true on success gcc_unused Error &error) {
*/ return true;
static inline bool }
encoder_end(Encoder *encoder, Error &error)
{
assert(encoder->open);
assert(!encoder->end);
#ifndef NDEBUG
encoder->end = true;
#endif
/* this method is optional */
return encoder->plugin.end != nullptr
? encoder->plugin.end(encoder, error)
: true;
}
/**
* Flushes an encoder object, make everything which might currently be
* buffered available by encoder_read().
*
* @param encoder the encoder
* @return true on success
*/
static inline bool
encoder_flush(Encoder *encoder, Error &error)
{
assert(encoder->open);
assert(!encoder->pre_tag);
assert(!encoder->tag);
assert(!encoder->end);
/* this method is optional */
return encoder->plugin.flush != nullptr
? encoder->plugin.flush(encoder, error)
: true;
}
/**
* Prepare for sending a tag to the encoder. This is used by some
* encoders to flush the previous sub-stream, in preparation to begin
* a new one.
*
* @param encoder the encoder
* @return true on success
*/
static inline bool
encoder_pre_tag(Encoder *encoder, Error &error)
{
assert(encoder->open);
assert(!encoder->pre_tag);
assert(!encoder->tag);
assert(!encoder->end);
/* this method is optional */
bool success = encoder->plugin.pre_tag != nullptr
? encoder->plugin.pre_tag(encoder, error)
: true;
#ifndef NDEBUG
encoder->pre_tag = success;
#endif
return success;
}
/** /**
* Sends a tag to the encoder. * Writes raw PCM data to the encoder.
* *
* Instructions: call encoder_pre_tag(); then obtain flushed data with * @param data the buffer containing PCM samples
* encoder_read(); finally call encoder_tag(). * @param length the length of the buffer in bytes
* * @return true on success
* @param encoder the encoder */
* @param tag the tag object virtual bool Write(const void *data, size_t length,
* @return true on success Error &error) = 0;
*/
static inline bool
encoder_tag(Encoder *encoder, const Tag &tag, Error &error)
{
assert(encoder->open);
assert(!encoder->pre_tag);
assert(encoder->tag);
assert(!encoder->end);
#ifndef NDEBUG /**
encoder->tag = false; * Reads encoded data from the encoder.
#endif *
* Call this repeatedly until no more data is returned.
*
* @param dest the destination buffer to copy to
* @param length the maximum length of the destination buffer
* @return the number of bytes written to #dest
*/
virtual size_t Read(void *dest, size_t length) = 0;
};
/* this method is optional */ struct PreparedEncoder {
return encoder->plugin.tag != nullptr const EncoderPlugin &plugin;
? encoder->plugin.tag(encoder, tag, error)
: true;
}
/** explicit PreparedEncoder(const EncoderPlugin &_plugin)
* Writes raw PCM data to the encoder. :plugin(_plugin) {}
*
* @param encoder the encoder
* @param data the buffer containing PCM samples
* @param length the length of the buffer in bytes
* @return true on success
*/
static inline bool
encoder_write(Encoder *encoder, const void *data, size_t length,
Error &error)
{
assert(encoder->open);
assert(!encoder->pre_tag);
assert(!encoder->tag);
assert(!encoder->end);
return encoder->plugin.write(encoder, data, length, error);
}
/** /**
* Reads encoded data from the encoder. * Frees an #Encoder object.
* */
* Call this repeatedly until no more data is returned. void Dispose() {
* plugin.finish(this);
* @param encoder the encoder }
* @param dest the destination buffer to copy to
* @param length the maximum length of the destination buffer
* @return the number of bytes written to #dest
*/
static inline size_t
encoder_read(Encoder *encoder, void *dest, size_t length)
{
assert(encoder->open);
assert(!encoder->pre_tag || !encoder->tag);
#ifndef NDEBUG /**
if (encoder->pre_tag) { * Opens the object. You must call this prior to using it.
encoder->pre_tag = false; * Before you free it, you must call Close(). You may open
encoder->tag = true; * 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);
} }
#endif
return encoder->plugin.read(encoder, dest, length); };
}
/** /**
* 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);
bool (*open)(Encoder *encoder,
AudioFormat &audio_format,
Error &error);
void (*close)(Encoder *encoder);
bool (*end)(Encoder *encoder, Error &error); void (*finish)(PreparedEncoder *encoder);
bool (*flush)(Encoder *encoder, Error &error); Encoder *(*open)(PreparedEncoder *encoder,
AudioFormat &audio_format,
bool (*pre_tag)(Encoder *encoder, Error &error); Error &error);
bool (*tag)(Encoder *encoder, const Tag &tag,
Error &error);
bool (*write)(Encoder *encoder,
const void *data, size_t length,
Error &error);
size_t (*read)(Encoder *encoder, void *dest, size_t length);
const char *(*get_mime_type)(Encoder *encoder); const char *(*get_mime_type)(PreparedEncoder *encoder);
}; };
/** /**
...@@ -67,7 +48,7 @@ struct EncoderPlugin { ...@@ -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);
......
...@@ -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;
public:
ShineEncoder(AudioFormat _audio_format, shine_t _shine)
:Encoder(false),
audio_format(_audio_format), shine(_shine),
frame_size(shine_samples_per_pass(shine)),
stereo{new int16_t[frame_size], new int16_t[frame_size]},
output_buffer(BUFFER_INIT_SIZE) {}
~ShineEncoder() override {
if (input_pos > SHINE_MAX_SAMPLES) {
/* write zero chunk */
input_pos = 0;
WriteChunk(true);
}
shine_close(shine);
delete[] stereo[0];
delete[] stereo[1];
}
bool WriteChunk(bool flush);
/* virtual methods from class Encoder */
bool End(Error &error) override {
return Flush(error);
}
bool Flush(Error &) override;
bool Write(const void *data, size_t length, Error &) override;
size_t Read(void *dest, size_t length) override {
return output_buffer.Read((uint8_t *)dest, length);
}
};
ShineEncoder():encoder(shine_encoder_plugin){} struct PreparedShineEncoder {
PreparedEncoder encoder;
shine_config_t config;
PreparedShineEncoder():encoder(shine_encoder_plugin) {}
bool Configure(const ConfigBlock &block, Error &error); bool Configure(const ConfigBlock &block, Error &error);
bool Setup(Error &error); bool Setup(Error &error);
bool WriteChunk(bool flush);
}; };
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
shine_encoder_open(Encoder *_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 static Encoder *
shine_encoder_close(Encoder *_encoder) shine_encoder_open(PreparedEncoder *_encoder, AudioFormat &audio_format,
Error &error)
{ {
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;
TwolameEncoder():encoder(twolame_encoder_plugin) {} public:
TwolameEncoder(const AudioFormat _audio_format,
twolame_options *_options)
:Encoder(false),
audio_format(_audio_format), options(_options) {}
~TwolameEncoder() override;
bool Configure(const ConfigBlock &block, Error &error);
/* virtual methods from class Encoder */
bool End(Error &) override {
flush = true;
return true;
}
bool Flush(Error &) override {
flush = true;
return true;
}
bool Write(const void *data, size_t length, Error &) override;
size_t Read(void *dest, size_t length) override;
};
struct PreparedTwolameEncoder final {
PreparedEncoder encoder;
float quality;
int bitrate;
PreparedTwolameEncoder():encoder(twolame_encoder_plugin) {}
bool Configure(const ConfigBlock &block, Error &error); 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 TwolameEncoder::~TwolameEncoder()
twolame_encoder_close(Encoder *_encoder)
{ {
TwolameEncoder *encoder = (TwolameEncoder *)_encoder; twolame_close(&options);
twolame_close(&encoder->options);
} }
static bool bool
twolame_encoder_flush(Encoder *_encoder, gcc_unused Error &error) TwolameEncoder::Write(const void *data, size_t length,
{
TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
encoder->flush = true;
return true;
}
static bool
twolame_encoder_write(Encoder *_encoder,
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 Reinit(Error &error); bool Open(float quality, int bitrate, AudioFormat &audio_format,
Error &error);
/* virtual methods from class Encoder */
bool End(Error &error) override {
return PreTag(error);
}
bool Flush(Error &error) override;
bool PreTag(Error &error) override;
bool SendTag(const Tag &tag, Error &error) override;
bool Write(const void *data, size_t length, Error &) override;
size_t Read(void *dest, size_t length) override {
return stream.PageOut(dest, length);
}
private:
void HeaderOut(vorbis_comment &vc); void 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; auto *e = new VorbisEncoder();
if (!e->Open(encoder.quality, encoder.bitrate, audio_format, error)) {
if (!encoder.Reinit(error)) delete e;
return false; return nullptr;
}
encoder.SendHeader();
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,15 +250,14 @@ HttpdOutput::ReadPage() ...@@ -250,15 +250,14 @@ 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