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