encoder/Interface: move instance methods to abstract class
Rename struct Encoder to PreparedEncoder, and add a new (abstract) class Encoder which represents one encoder instance.
This commit is contained in:
parent
69bf835059
commit
e7edc02647
@ -21,30 +21,108 @@
|
|||||||
#define MPD_ENCODER_INTERFACE_HXX
|
#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;
|
||||||
|
|
||||||
|
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;
|
const EncoderPlugin &plugin;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
explicit PreparedEncoder(const EncoderPlugin &_plugin)
|
||||||
bool open, pre_tag, tag, end;
|
:plugin(_plugin) {}
|
||||||
#endif
|
|
||||||
|
|
||||||
explicit Encoder(const EncoderPlugin &_plugin)
|
|
||||||
:plugin(_plugin)
|
|
||||||
#ifndef NDEBUG
|
|
||||||
, open(false)
|
|
||||||
#endif
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees an #Encoder object.
|
* Frees an #Encoder object.
|
||||||
*/
|
*/
|
||||||
void Dispose() {
|
void Dispose() {
|
||||||
assert(!open);
|
|
||||||
|
|
||||||
plugin.finish(this);
|
plugin.finish(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,191 +139,19 @@ struct Encoder {
|
|||||||
* may modify the struct to adapt it to its abilities
|
* 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) {
|
Encoder *Open(AudioFormat &audio_format, Error &error) {
|
||||||
assert(!open);
|
return plugin.open(this, audio_format, error);
|
||||||
|
|
||||||
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
|
|
||||||
* 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.
|
* Get mime type of encoded content.
|
||||||
*
|
*
|
||||||
* @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 @@
|
|||||||
#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);
|
||||||
|
|
||||||
|
void (*finish)(PreparedEncoder *encoder);
|
||||||
|
|
||||||
|
Encoder *(*open)(PreparedEncoder *encoder,
|
||||||
|
AudioFormat &audio_format,
|
||||||
Error &error);
|
Error &error);
|
||||||
|
|
||||||
void (*finish)(Encoder *encoder);
|
const char *(*get_mime_type)(PreparedEncoder *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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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)
|
|||||||
/* 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 @@
|
|||||||
|
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
|
|
||||||
struct Encoder;
|
|
||||||
class OutputStream;
|
class OutputStream;
|
||||||
|
class Encoder;
|
||||||
|
|
||||||
void
|
void
|
||||||
EncoderToOutputStream(OutputStream &os, Encoder &encoder);
|
EncoderToOutputStream(OutputStream &os, Encoder &encoder);
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "pcm/PcmBuffer.hxx"
|
#include "pcm/PcmBuffer.hxx"
|
||||||
#include "config/ConfigError.hxx"
|
#include "config/ConfigError.hxx"
|
||||||
#include "util/Manual.hxx"
|
|
||||||
#include "util/DynamicFifoBuffer.hxx"
|
#include "util/DynamicFifoBuffer.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
@ -34,13 +33,10 @@
|
|||||||
#error libFLAC is too old
|
#error libFLAC is too old
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct flac_encoder {
|
class FlacEncoder final : public Encoder {
|
||||||
Encoder encoder;
|
const AudioFormat audio_format;
|
||||||
|
|
||||||
AudioFormat audio_format;
|
FLAC__StreamEncoder *const fse;
|
||||||
unsigned compression;
|
|
||||||
|
|
||||||
FLAC__StreamEncoder *fse;
|
|
||||||
|
|
||||||
PcmBuffer expand_buffer;
|
PcmBuffer expand_buffer;
|
||||||
|
|
||||||
@ -48,29 +44,76 @@ struct flac_encoder {
|
|||||||
* This buffer will hold encoded data from libFLAC until it is
|
* This buffer will hold encoded data from libFLAC until it is
|
||||||
* picked up with flac_encoder_read().
|
* picked up with flac_encoder_read().
|
||||||
*/
|
*/
|
||||||
Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
|
DynamicFifoBuffer<uint8_t> output_buffer;
|
||||||
|
|
||||||
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 constexpr Domain flac_encoder_domain("vorbis_encoder");
|
||||||
|
|
||||||
static bool
|
bool
|
||||||
flac_encoder_configure(struct flac_encoder *encoder, const ConfigBlock &block,
|
PreparedFlacEncoder::Configure(const ConfigBlock &block, Error &)
|
||||||
gcc_unused Error &error)
|
|
||||||
{
|
{
|
||||||
encoder->compression = block.GetBlockValue("compression", 5u);
|
compression = block.GetBlockValue("compression", 5u);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Encoder *
|
static PreparedEncoder *
|
||||||
flac_encoder_init(const ConfigBlock &block, Error &error)
|
flac_encoder_init(const ConfigBlock &block, Error &error)
|
||||||
{
|
{
|
||||||
flac_encoder *encoder = new flac_encoder();
|
auto *encoder = new PreparedFlacEncoder();
|
||||||
|
|
||||||
/* load configuration from "block" */
|
/* load configuration from "block" */
|
||||||
if (!flac_encoder_configure(encoder, block, error)) {
|
if (!encoder->Configure(block, error)) {
|
||||||
/* configuration has failed, roll back and return error */
|
/* configuration has failed, roll back and return error */
|
||||||
delete encoder;
|
delete encoder;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -80,9 +123,9 @@ flac_encoder_init(const ConfigBlock &block, Error &error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
flac_encoder_finish(Encoder *_encoder)
|
flac_encoder_finish(PreparedEncoder *_encoder)
|
||||||
{
|
{
|
||||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
auto *encoder = (PreparedFlacEncoder *)_encoder;
|
||||||
|
|
||||||
/* the real libFLAC cleanup was already performed by
|
/* the real libFLAC cleanup was already performed by
|
||||||
flac_encoder_close(), so no real work here */
|
flac_encoder_close(), so no real work here */
|
||||||
@ -90,71 +133,67 @@ flac_encoder_finish(Encoder *_encoder)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample,
|
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||||
|
const AudioFormat &audio_format, unsigned bits_per_sample,
|
||||||
Error &error)
|
Error &error)
|
||||||
{
|
{
|
||||||
if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
|
if (!FLAC__stream_encoder_set_compression_level(fse, compression)) {
|
||||||
encoder->compression)) {
|
|
||||||
error.Format(config_domain,
|
error.Format(config_domain,
|
||||||
"error setting flac compression to %d",
|
"error setting flac compression to %d",
|
||||||
encoder->compression);
|
compression);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !FLAC__stream_encoder_set_channels(encoder->fse,
|
if (!FLAC__stream_encoder_set_channels(fse, audio_format.channels)) {
|
||||||
encoder->audio_format.channels)) {
|
|
||||||
error.Format(config_domain,
|
error.Format(config_domain,
|
||||||
"error setting flac channels num to %d",
|
"error setting flac channels num to %d",
|
||||||
encoder->audio_format.channels);
|
audio_format.channels);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse,
|
|
||||||
bits_per_sample)) {
|
if (!FLAC__stream_encoder_set_bits_per_sample(fse, bits_per_sample)) {
|
||||||
error.Format(config_domain,
|
error.Format(config_domain,
|
||||||
"error setting flac bit format to %d",
|
"error setting flac bit format to %d",
|
||||||
bits_per_sample);
|
bits_per_sample);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse,
|
|
||||||
encoder->audio_format.sample_rate)) {
|
if (!FLAC__stream_encoder_set_sample_rate(fse,
|
||||||
|
audio_format.sample_rate)) {
|
||||||
error.Format(config_domain,
|
error.Format(config_domain,
|
||||||
"error setting flac sample rate to %d",
|
"error setting flac sample rate to %d",
|
||||||
encoder->audio_format.sample_rate);
|
audio_format.sample_rate);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FLAC__StreamEncoderWriteStatus
|
bool
|
||||||
flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse,
|
FlacEncoder::Init(Error &error)
|
||||||
const FLAC__byte data[],
|
|
||||||
size_t bytes,
|
|
||||||
gcc_unused unsigned samples,
|
|
||||||
gcc_unused unsigned current_frame, void *client_data)
|
|
||||||
{
|
{
|
||||||
struct flac_encoder *encoder = (struct flac_encoder *) client_data;
|
/* this immediately outputs data through callback */
|
||||||
|
|
||||||
//transfer data to buffer
|
auto init_status =
|
||||||
encoder->output_buffer->Append((const uint8_t *)data, bytes);
|
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
|
static Encoder *
|
||||||
flac_encoder_close(Encoder *_encoder)
|
flac_encoder_open(PreparedEncoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||||
{
|
{
|
||||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
auto *encoder = (PreparedFlacEncoder *)_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;
|
|
||||||
unsigned bits_per_sample;
|
unsigned bits_per_sample;
|
||||||
|
|
||||||
/* FIXME: flac should support 32bit as well */
|
/* FIXME: flac should support 32bit as well */
|
||||||
@ -176,51 +215,26 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
|||||||
audio_format.format = SampleFormat::S24_P32;
|
audio_format.format = SampleFormat::S24_P32;
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder->audio_format = audio_format;
|
|
||||||
|
|
||||||
/* allocate the encoder */
|
/* allocate the encoder */
|
||||||
encoder->fse = FLAC__stream_encoder_new();
|
auto fse = FLAC__stream_encoder_new();
|
||||||
if (encoder->fse == nullptr) {
|
if (fse == nullptr) {
|
||||||
error.Set(flac_encoder_domain, "flac_new() failed");
|
error.Set(flac_encoder_domain, "FLAC__stream_encoder_new() failed");
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!flac_encoder_setup(encoder, bits_per_sample, error)) {
|
if (!flac_encoder_setup(fse, encoder->compression,
|
||||||
FLAC__stream_encoder_delete(encoder->fse);
|
audio_format, bits_per_sample, error)) {
|
||||||
return false;
|
FLAC__stream_encoder_delete(fse);
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder->output_buffer.Construct(8192);
|
auto *e = new FlacEncoder(audio_format, fse);
|
||||||
|
if (!e->Init(error)) {
|
||||||
/* this immediately outputs data through callback */
|
delete e;
|
||||||
|
return nullptr;
|
||||||
{
|
|
||||||
FLAC__StreamEncoderInitStatus init_status;
|
|
||||||
|
|
||||||
init_status = FLAC__stream_encoder_init_stream(encoder->fse,
|
|
||||||
flac_write_callback,
|
|
||||||
nullptr, nullptr, nullptr, encoder);
|
|
||||||
|
|
||||||
if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
|
|
||||||
error.Format(flac_encoder_domain,
|
|
||||||
"failed to initialize encoder: %s\n",
|
|
||||||
FLAC__StreamEncoderInitStatusString[init_status]);
|
|
||||||
flac_encoder_close(_encoder);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return e;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool
|
|
||||||
flac_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
|
|
||||||
{
|
|
||||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
|
||||||
|
|
||||||
(void) FLAC__stream_encoder_finish(encoder->fse);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@ -241,31 +255,27 @@ pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
bool
|
||||||
flac_encoder_write(Encoder *_encoder,
|
FlacEncoder::Write(const void *data, size_t length, Error &error)
|
||||||
const void *data, size_t length,
|
|
||||||
gcc_unused Error &error)
|
|
||||||
{
|
{
|
||||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
|
||||||
unsigned num_frames, num_samples;
|
|
||||||
void *exbuffer;
|
void *exbuffer;
|
||||||
const void *buffer = nullptr;
|
const void *buffer = nullptr;
|
||||||
|
|
||||||
/* format conversion */
|
/* format conversion */
|
||||||
|
|
||||||
num_frames = length / encoder->audio_format.GetFrameSize();
|
const unsigned num_frames = length / audio_format.GetFrameSize();
|
||||||
num_samples = num_frames * encoder->audio_format.channels;
|
const unsigned num_samples = num_frames * audio_format.channels;
|
||||||
|
|
||||||
switch (encoder->audio_format.format) {
|
switch (audio_format.format) {
|
||||||
case SampleFormat::S8:
|
case SampleFormat::S8:
|
||||||
exbuffer = encoder->expand_buffer.Get(length * 4);
|
exbuffer = expand_buffer.Get(length * 4);
|
||||||
pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data,
|
pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data,
|
||||||
num_samples);
|
num_samples);
|
||||||
buffer = exbuffer;
|
buffer = exbuffer;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SampleFormat::S16:
|
case SampleFormat::S16:
|
||||||
exbuffer = encoder->expand_buffer.Get(length * 2);
|
exbuffer = expand_buffer.Get(length * 2);
|
||||||
pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data,
|
pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data,
|
||||||
num_samples);
|
num_samples);
|
||||||
buffer = exbuffer;
|
buffer = exbuffer;
|
||||||
@ -284,7 +294,7 @@ flac_encoder_write(Encoder *_encoder,
|
|||||||
|
|
||||||
/* feed samples to encoder */
|
/* feed samples to encoder */
|
||||||
|
|
||||||
if (!FLAC__stream_encoder_process_interleaved(encoder->fse,
|
if (!FLAC__stream_encoder_process_interleaved(fse,
|
||||||
(const FLAC__int32 *)buffer,
|
(const FLAC__int32 *)buffer,
|
||||||
num_frames)) {
|
num_frames)) {
|
||||||
error.Set(flac_encoder_domain, "flac encoder process failed");
|
error.Set(flac_encoder_domain, "flac encoder process failed");
|
||||||
@ -294,16 +304,8 @@ flac_encoder_write(Encoder *_encoder,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t
|
|
||||||
flac_encoder_read(Encoder *_encoder, void *dest, size_t length)
|
|
||||||
{
|
|
||||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
|
||||||
|
|
||||||
return encoder->output_buffer->Read((uint8_t *)dest, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
flac_encoder_get_mime_type(gcc_unused Encoder *_encoder)
|
flac_encoder_get_mime_type(gcc_unused PreparedEncoder *_encoder)
|
||||||
{
|
{
|
||||||
return "audio/flac";
|
return "audio/flac";
|
||||||
}
|
}
|
||||||
@ -313,13 +315,6 @@ const EncoderPlugin flac_encoder_plugin = {
|
|||||||
flac_encoder_init,
|
flac_encoder_init,
|
||||||
flac_encoder_finish,
|
flac_encoder_finish,
|
||||||
flac_encoder_open,
|
flac_encoder_open,
|
||||||
flac_encoder_close,
|
|
||||||
flac_encoder_flush,
|
|
||||||
flac_encoder_flush,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
flac_encoder_write,
|
|
||||||
flac_encoder_read,
|
|
||||||
flac_encoder_get_mime_type,
|
flac_encoder_get_mime_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
#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 @@
|
|||||||
#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;
|
||||||
|
|
||||||
|
lame_global_flags *const gfp;
|
||||||
|
|
||||||
|
ReusableArray<unsigned char, 32768> output_buffer;
|
||||||
|
unsigned char *output_begin = nullptr, *output_end = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LameEncoder(const AudioFormat _audio_format,
|
||||||
|
lame_global_flags *_gfp)
|
||||||
|
:Encoder(false),
|
||||||
|
audio_format(_audio_format), gfp(_gfp) {}
|
||||||
|
|
||||||
|
~LameEncoder() override;
|
||||||
|
|
||||||
|
/* virtual methods from class Encoder */
|
||||||
|
bool Write(const void *data, size_t length, Error &) override;
|
||||||
|
size_t Read(void *dest, size_t length) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreparedLameEncoder final {
|
||||||
|
PreparedEncoder encoder;
|
||||||
|
|
||||||
AudioFormat audio_format;
|
|
||||||
float quality;
|
float quality;
|
||||||
int bitrate;
|
int bitrate;
|
||||||
|
|
||||||
lame_global_flags *gfp;
|
PreparedLameEncoder():encoder(lame_encoder_plugin) {}
|
||||||
|
|
||||||
Manual<ReusableArray<unsigned char, 32768>> output_buffer;
|
|
||||||
unsigned char *output_begin, *output_end;
|
|
||||||
|
|
||||||
LameEncoder():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 {
|
|||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
} 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)
|
|||||||
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 = {
|
|||||||
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 @@
|
|||||||
#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 = {
|
|||||||
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 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
struct OpusEncoder {
|
namespace {
|
||||||
/** the base class */
|
|
||||||
Encoder encoder;
|
|
||||||
|
|
||||||
/* configuration */
|
class OpusEncoder final : public Encoder {
|
||||||
|
const AudioFormat audio_format;
|
||||||
|
|
||||||
opus_int32 bitrate;
|
const size_t frame_size;
|
||||||
int complexity;
|
|
||||||
int signal;
|
|
||||||
|
|
||||||
/* runtime information */
|
const size_t buffer_frames, buffer_size;
|
||||||
|
size_t buffer_position = 0;
|
||||||
|
uint8_t *const buffer;
|
||||||
|
|
||||||
AudioFormat audio_format;
|
::OpusEncoder *const enc;
|
||||||
|
|
||||||
size_t frame_size;
|
|
||||||
|
|
||||||
size_t buffer_frames, buffer_size, buffer_position;
|
|
||||||
uint8_t *buffer;
|
|
||||||
|
|
||||||
OpusEncoder *enc;
|
|
||||||
|
|
||||||
unsigned char buffer2[1275 * 3 + 7];
|
unsigned char buffer2[1275 * 3 + 7];
|
||||||
|
|
||||||
@ -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;
|
||||||
|
|
||||||
|
size_t Read(void *dest, size_t length) override;
|
||||||
|
|
||||||
|
private:
|
||||||
bool DoEncode(bool eos, Error &error);
|
bool DoEncode(bool eos, Error &error);
|
||||||
|
|
||||||
bool End(Error &error);
|
|
||||||
bool Flush(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();
|
||||||
|
};
|
||||||
|
|
||||||
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");
|
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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 @@
|
|||||||
#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"
|
|||||||
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;
|
||||||
|
|
||||||
|
/* workaround for bug:
|
||||||
|
https://github.com/savonet/shine/issues/11 */
|
||||||
|
size_t input_pos = SHINE_MAX_SAMPLES + 1;
|
||||||
|
|
||||||
|
int16_t *stereo[CHANNELS];
|
||||||
|
|
||||||
|
DynamicFifoBuffer<uint8_t> output_buffer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ShineEncoder(AudioFormat _audio_format, shine_t _shine)
|
||||||
|
:Encoder(false),
|
||||||
|
audio_format(_audio_format), shine(_shine),
|
||||||
|
frame_size(shine_samples_per_pass(shine)),
|
||||||
|
stereo{new int16_t[frame_size], new int16_t[frame_size]},
|
||||||
|
output_buffer(BUFFER_INIT_SIZE) {}
|
||||||
|
|
||||||
|
~ShineEncoder() override {
|
||||||
|
if (input_pos > SHINE_MAX_SAMPLES) {
|
||||||
|
/* write zero chunk */
|
||||||
|
input_pos = 0;
|
||||||
|
WriteChunk(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
shine_close(shine);
|
||||||
|
delete[] stereo[0];
|
||||||
|
delete[] stereo[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteChunk(bool flush);
|
||||||
|
|
||||||
|
/* virtual methods from class Encoder */
|
||||||
|
bool End(Error &error) override {
|
||||||
|
return Flush(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Flush(Error &) override;
|
||||||
|
|
||||||
|
bool Write(const void *data, size_t length, Error &) override;
|
||||||
|
|
||||||
|
size_t Read(void *dest, size_t length) override {
|
||||||
|
return output_buffer.Read((uint8_t *)dest, length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreparedShineEncoder {
|
||||||
|
PreparedEncoder encoder;
|
||||||
|
|
||||||
shine_config_t config;
|
shine_config_t config;
|
||||||
|
|
||||||
size_t frame_size;
|
PreparedShineEncoder():encoder(shine_encoder_plugin) {}
|
||||||
size_t input_pos;
|
|
||||||
int16_t *stereo[CHANNELS];
|
|
||||||
|
|
||||||
Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
|
|
||||||
|
|
||||||
ShineEncoder():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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
config.wave.samplerate,
|
config.wave.samplerate,
|
||||||
config.mpeg.bitr);
|
config.mpeg.bitr);
|
||||||
|
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
shine = shine_initialise(&config);
|
auto shine = shine_initialise(&config);
|
||||||
|
if (!shine)
|
||||||
if (!shine) {
|
|
||||||
error.Format(config_domain,
|
error.Format(config_domain,
|
||||||
"error initializing shine.");
|
"error initializing shine.");
|
||||||
|
|
||||||
return false;
|
return shine;
|
||||||
}
|
|
||||||
|
|
||||||
frame_size = shine_samples_per_pass(shine);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static Encoder *
|
||||||
shine_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
shine_encoder_open(PreparedEncoder *_encoder, AudioFormat &audio_format,
|
||||||
|
Error &error)
|
||||||
{
|
{
|
||||||
ShineEncoder *encoder = (ShineEncoder *)_encoder;
|
auto *encoder = (PreparedShineEncoder *)_encoder;
|
||||||
|
|
||||||
audio_format.format = SampleFormat::S16;
|
auto shine = SetupShine(encoder->config, audio_format, error);
|
||||||
audio_format.channels = CHANNELS;
|
if (!shine)
|
||||||
encoder->audio_format = audio_format;
|
return nullptr;
|
||||||
|
|
||||||
if (!encoder->Setup(error))
|
return new ShineEncoder(audio_format, shine);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -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)
|
|||||||
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 = {
|
|||||||
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 @@
|
|||||||
#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 {
|
|||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
} 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)
|
|||||||
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 <=
|
if (output_buffer_position == output_buffer_length && flush) {
|
||||||
encoder->output_buffer_length);
|
int ret = twolame_encode_flush(options, output_buffer,
|
||||||
|
sizeof(output_buffer));
|
||||||
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 (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 = {
|
|||||||
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 @@
|
|||||||
|
|
||||||
#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 {
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
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()
|
|||||||
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;
|
auto *e = new VorbisEncoder();
|
||||||
|
if (!e->Open(encoder.quality, encoder.bitrate, audio_format, error)) {
|
||||||
|
delete e;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
encoder.audio_format = audio_format;
|
return e;
|
||||||
|
|
||||||
if (!encoder.Reinit(error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
encoder.SendHeader();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -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()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|||||||
|
|
||||||
/* 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,
|
|||||||
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 = {
|
|||||||
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 @@
|
|||||||
#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 @@
|
|||||||
|
|
||||||
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,
|
|||||||
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));
|
buffer.Append(sizeof(*header));
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
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,
|
|||||||
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,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = {
|
|||||||
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 {
|
|||||||
/**
|
/**
|
||||||
* 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 {
|
|||||||
: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)
|
|||||||
|
|
||||||
/* 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)
|
|||||||
|
|
||||||
/* 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)
|
|||||||
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)
|
|||||||
|
|
||||||
/* 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)
|
|||||||
|
|
||||||
/* 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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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 {
|
|||||||
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()
|
|||||||
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)
|
|||||||
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)
|
|||||||
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
|
|||||||
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)
|
|||||||
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
|
|||||||
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)
|
|||||||
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;
|
|||||||
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 {
|
|||||||
/**
|
/**
|
||||||
* The configured encoder plugin.
|
* The configured encoder plugin.
|
||||||
*/
|
*/
|
||||||
|
PreparedEncoder *prepared_encoder = nullptr;
|
||||||
Encoder *encoder;
|
Encoder *encoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
|||||||
|
|
||||||
/* 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
|
|||||||
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()
|
|||||||
/* 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)
|
|||||||
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()
|
|||||||
if (header != nullptr)
|
if (header != nullptr)
|
||||||
header->Unref();
|
header->Unref();
|
||||||
|
|
||||||
encoder->Close();
|
delete encoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -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)
|
|||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
|
|
||||||
/* 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)
|
|||||||
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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user