flac encoder: enable Ogg FLAC and Ogg chaining
refactors GenerateOggSerial into a generic GenerateSerial utility, under the util lib. libFLAC may be encoded without Ogg support. If Ogg support is disabled, libFLAC will still export Ogg-related methods (like setting a serial number), and throw a runtime error when initializing an Ogg stream. GenerateOggSerial does not depend on libogg. Refactoring it into a generic GenerateSerial prevents having to add build-time checks for libogg within the FLAC encoder plugin.
This commit is contained in:
parent
c3226a3195
commit
87fa6bca54
@ -583,6 +583,10 @@ Encodes into `FLAC <https://xiph.org/flac/>`_ (lossless).
|
|||||||
- Description
|
- Description
|
||||||
* - **compression**
|
* - **compression**
|
||||||
- Sets the libFLAC compression level. The levels range from 0 (fastest, least compression) to 8 (slowest, most compression).
|
- Sets the libFLAC compression level. The levels range from 0 (fastest, least compression) to 8 (slowest, most compression).
|
||||||
|
* - **oggflac yes|no**
|
||||||
|
- Configures if the stream should be Ogg FLAC versus native FLAC. Defaults to "no" (use native FLAC).
|
||||||
|
* - **oggchaining yes|no**
|
||||||
|
- Configures if the stream should use Ogg Chaining for in-stream metadata. Defaults to "no". Setting this to "yes" also enables Ogg FLAC.
|
||||||
|
|
||||||
lame
|
lame
|
||||||
----
|
----
|
||||||
|
@ -23,8 +23,11 @@
|
|||||||
#include "pcm/Buffer.hxx"
|
#include "pcm/Buffer.hxx"
|
||||||
#include "util/DynamicFifoBuffer.hxx"
|
#include "util/DynamicFifoBuffer.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/Serial.hxx"
|
||||||
|
#include "util/StringUtil.hxx"
|
||||||
|
|
||||||
#include <FLAC/stream_encoder.h>
|
#include <FLAC/stream_encoder.h>
|
||||||
|
#include <FLAC/metadata.h>
|
||||||
|
|
||||||
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||||
#error libFLAC is too old
|
#error libFLAC is too old
|
||||||
@ -34,6 +37,7 @@ class FlacEncoder final : public Encoder {
|
|||||||
const AudioFormat audio_format;
|
const AudioFormat audio_format;
|
||||||
|
|
||||||
FLAC__StreamEncoder *const fse;
|
FLAC__StreamEncoder *const fse;
|
||||||
|
const unsigned compression;
|
||||||
|
|
||||||
PcmBuffer expand_buffer;
|
PcmBuffer expand_buffer;
|
||||||
|
|
||||||
@ -44,7 +48,7 @@ class FlacEncoder final : public Encoder {
|
|||||||
DynamicFifoBuffer<uint8_t> output_buffer;
|
DynamicFifoBuffer<uint8_t> output_buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse);
|
FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining);
|
||||||
|
|
||||||
~FlacEncoder() noexcept override {
|
~FlacEncoder() noexcept override {
|
||||||
FLAC__stream_encoder_delete(fse);
|
FLAC__stream_encoder_delete(fse);
|
||||||
@ -56,9 +60,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Flush() override {
|
void Flush() override {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreTag() override {
|
||||||
(void) FLAC__stream_encoder_finish(fse);
|
(void) FLAC__stream_encoder_finish(fse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SendTag(const Tag &tag) override;
|
||||||
|
|
||||||
void Write(const void *data, size_t length) override;
|
void Write(const void *data, size_t length) override;
|
||||||
|
|
||||||
size_t Read(void *dest, size_t length) noexcept override {
|
size_t Read(void *dest, size_t length) noexcept override {
|
||||||
@ -80,6 +89,8 @@ private:
|
|||||||
|
|
||||||
class PreparedFlacEncoder final : public PreparedEncoder {
|
class PreparedFlacEncoder final : public PreparedEncoder {
|
||||||
const unsigned compression;
|
const unsigned compression;
|
||||||
|
const bool oggchaining;
|
||||||
|
const bool oggflac;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PreparedFlacEncoder(const ConfigBlock &block);
|
explicit PreparedFlacEncoder(const ConfigBlock &block);
|
||||||
@ -88,12 +99,16 @@ public:
|
|||||||
Encoder *Open(AudioFormat &audio_format) override;
|
Encoder *Open(AudioFormat &audio_format) override;
|
||||||
|
|
||||||
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
[[nodiscard]] const char *GetMimeType() const noexcept override {
|
||||||
return "audio/flac";
|
if(oggflac)
|
||||||
|
return "audio/ogg";
|
||||||
|
return "audio/flac";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block)
|
PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block)
|
||||||
:compression(block.GetBlockValue("compression", 5U))
|
:compression(block.GetBlockValue("compression", 5U)),
|
||||||
|
oggchaining(block.GetBlockValue("oggchaining",false)),
|
||||||
|
oggflac(block.GetBlockValue("oggflac",false) || oggchaining)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,8 +120,23 @@ flac_encoder_init(const ConfigBlock &block)
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||||
const AudioFormat &audio_format, unsigned bits_per_sample)
|
const AudioFormat &audio_format)
|
||||||
{
|
{
|
||||||
|
unsigned bits_per_sample;
|
||||||
|
|
||||||
|
switch (audio_format.format) {
|
||||||
|
case SampleFormat::S8:
|
||||||
|
bits_per_sample = 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SampleFormat::S16:
|
||||||
|
bits_per_sample = 16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
bits_per_sample = 24;
|
||||||
|
}
|
||||||
|
|
||||||
if (!FLAC__stream_encoder_set_compression_level(fse, compression))
|
if (!FLAC__stream_encoder_set_compression_level(fse, compression))
|
||||||
throw FormatRuntimeError("error setting flac compression to %d",
|
throw FormatRuntimeError("error setting flac compression to %d",
|
||||||
compression);
|
compression);
|
||||||
@ -123,16 +153,26 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
|||||||
audio_format.sample_rate))
|
audio_format.sample_rate))
|
||||||
throw FormatRuntimeError("error setting flac sample rate to %d",
|
throw FormatRuntimeError("error setting flac sample rate to %d",
|
||||||
audio_format.sample_rate);
|
audio_format.sample_rate);
|
||||||
|
|
||||||
|
if (!FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||||
|
GenerateSerial()))
|
||||||
|
throw FormatRuntimeError("error setting ogg serial number");
|
||||||
}
|
}
|
||||||
|
|
||||||
FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse)
|
FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining)
|
||||||
:Encoder(false),
|
:Encoder(_oggchaining),
|
||||||
audio_format(_audio_format), fse(_fse),
|
audio_format(_audio_format), fse(_fse),
|
||||||
|
compression(_compression),
|
||||||
output_buffer(8192)
|
output_buffer(8192)
|
||||||
{
|
{
|
||||||
/* this immediately outputs data through callback */
|
/* this immediately outputs data through callback */
|
||||||
|
|
||||||
auto init_status =
|
auto init_status = _oggflac ?
|
||||||
|
FLAC__stream_encoder_init_ogg_stream(fse,
|
||||||
|
nullptr, WriteCallback,
|
||||||
|
nullptr, nullptr, nullptr,
|
||||||
|
this)
|
||||||
|
:
|
||||||
FLAC__stream_encoder_init_stream(fse,
|
FLAC__stream_encoder_init_stream(fse,
|
||||||
WriteCallback,
|
WriteCallback,
|
||||||
nullptr, nullptr, nullptr,
|
nullptr, nullptr, nullptr,
|
||||||
@ -146,24 +186,17 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse)
|
|||||||
Encoder *
|
Encoder *
|
||||||
PreparedFlacEncoder::Open(AudioFormat &audio_format)
|
PreparedFlacEncoder::Open(AudioFormat &audio_format)
|
||||||
{
|
{
|
||||||
unsigned bits_per_sample;
|
|
||||||
|
|
||||||
/* FIXME: flac should support 32bit as well */
|
|
||||||
switch (audio_format.format) {
|
switch (audio_format.format) {
|
||||||
case SampleFormat::S8:
|
case SampleFormat::S8:
|
||||||
bits_per_sample = 8;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SampleFormat::S16:
|
case SampleFormat::S16:
|
||||||
bits_per_sample = 16;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SampleFormat::S24_P32:
|
case SampleFormat::S24_P32:
|
||||||
bits_per_sample = 24;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
bits_per_sample = 24;
|
|
||||||
audio_format.format = SampleFormat::S24_P32;
|
audio_format.format = SampleFormat::S24_P32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,16 +206,46 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
|
|||||||
throw std::runtime_error("FLAC__stream_encoder_new() failed");
|
throw std::runtime_error("FLAC__stream_encoder_new() failed");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
flac_encoder_setup(fse, compression,
|
flac_encoder_setup(fse, compression, audio_format);
|
||||||
audio_format, bits_per_sample);
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
FLAC__stream_encoder_delete(fse);
|
FLAC__stream_encoder_delete(fse);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FlacEncoder(audio_format, fse);
|
return new FlacEncoder(audio_format, fse, compression, oggflac, oggchaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FlacEncoder::SendTag(const Tag &tag)
|
||||||
|
{
|
||||||
|
/* re-initialize encoder since flac_encoder_finish resets everything */
|
||||||
|
flac_encoder_setup(fse, compression, audio_format);
|
||||||
|
|
||||||
|
FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
||||||
|
FLAC__StreamMetadata_VorbisComment_Entry entry;
|
||||||
|
|
||||||
|
for (const auto &item : tag) {
|
||||||
|
char name[64];
|
||||||
|
ToUpperASCII(name, tag_item_names[item.type], sizeof(name));
|
||||||
|
FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, name, item.value);
|
||||||
|
FLAC__metadata_object_vorbiscomment_append_comment(metadata, entry, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
FLAC__stream_encoder_set_metadata(fse,&metadata,1);
|
||||||
|
|
||||||
|
auto init_status = FLAC__stream_encoder_init_ogg_stream(fse,
|
||||||
|
nullptr, WriteCallback,
|
||||||
|
nullptr, nullptr, nullptr,
|
||||||
|
this);
|
||||||
|
|
||||||
|
FLAC__metadata_object_delete(metadata);
|
||||||
|
|
||||||
|
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
||||||
|
throw FormatRuntimeError("failed to initialize encoder: %s\n",
|
||||||
|
FLAC__StreamEncoderInitStatusString[init_status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples) noexcept
|
pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples) noexcept
|
||||||
{
|
{
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
#include "../EncoderAPI.hxx"
|
#include "../EncoderAPI.hxx"
|
||||||
#include "lib/xiph/OggStreamState.hxx"
|
#include "lib/xiph/OggStreamState.hxx"
|
||||||
#include "lib/xiph/OggPage.hxx"
|
#include "lib/xiph/OggPage.hxx"
|
||||||
#include "lib/xiph/OggSerial.hxx"
|
#include "util/Serial.hxx"
|
||||||
|
|
||||||
#include <ogg/ogg.h>
|
#include <ogg/ogg.h>
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ protected:
|
|||||||
public:
|
public:
|
||||||
OggEncoder(bool _implements_tag)
|
OggEncoder(bool _implements_tag)
|
||||||
:Encoder(_implements_tag),
|
:Encoder(_implements_tag),
|
||||||
stream(GenerateOggSerial()) {
|
stream(GenerateSerial()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* virtual methods from class Encoder */
|
/* virtual methods from class Encoder */
|
||||||
|
@ -405,7 +405,7 @@ OpusEncoder::PreTag()
|
|||||||
void
|
void
|
||||||
OpusEncoder::SendTag(const Tag &tag)
|
OpusEncoder::SendTag(const Tag &tag)
|
||||||
{
|
{
|
||||||
stream.Reinitialize(GenerateOggSerial());
|
stream.Reinitialize(GenerateSerial());
|
||||||
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
|
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead));
|
||||||
GenerateHeaders(&tag);
|
GenerateHeaders(&tag);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ VorbisEncoder::SendTag(const Tag &tag)
|
|||||||
|
|
||||||
/* reset ogg_stream_state and begin a new stream */
|
/* reset ogg_stream_state and begin a new stream */
|
||||||
|
|
||||||
stream.Reinitialize(GenerateOggSerial());
|
stream.Reinitialize(GenerateSerial());
|
||||||
|
|
||||||
/* send that vorbis_comment to the ogg_stream_state */
|
/* send that vorbis_comment to the ogg_stream_state */
|
||||||
|
|
||||||
|
@ -72,7 +72,6 @@ if libogg_dep.found()
|
|||||||
ogg = static_library(
|
ogg = static_library(
|
||||||
'ogg',
|
'ogg',
|
||||||
'OggVisitor.cxx',
|
'OggVisitor.cxx',
|
||||||
'OggSerial.cxx',
|
|
||||||
'OggSyncState.cxx',
|
'OggSyncState.cxx',
|
||||||
'OggFind.cxx',
|
'OggFind.cxx',
|
||||||
'OggPacket.cxx',
|
'OggPacket.cxx',
|
||||||
|
@ -17,18 +17,18 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "OggSerial.hxx"
|
#include "Serial.hxx"
|
||||||
#include "util/Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
static std::atomic_uint next_ogg_serial;
|
static std::atomic_uint next_serial;
|
||||||
|
|
||||||
int
|
int
|
||||||
GenerateOggSerial() noexcept
|
GenerateSerial() noexcept
|
||||||
{
|
{
|
||||||
unsigned serial = ++next_ogg_serial;
|
unsigned serial = ++next_serial;
|
||||||
if (gcc_unlikely(serial < 16)) {
|
if (gcc_unlikely(serial < 16)) {
|
||||||
/* first-time initialization: seed with a clock value,
|
/* first-time initialization: seed with a clock value,
|
||||||
which is random enough for our use */
|
which is random enough for our use */
|
||||||
@ -38,7 +38,7 @@ GenerateOggSerial() noexcept
|
|||||||
const auto now = steady_clock::now().time_since_epoch();
|
const auto now = steady_clock::now().time_since_epoch();
|
||||||
const auto now_ms = duration_cast<milliseconds>(now);
|
const auto now_ms = duration_cast<milliseconds>(now);
|
||||||
const unsigned seed = now_ms.count();
|
const unsigned seed = now_ms.count();
|
||||||
next_ogg_serial = serial = seed;
|
next_serial = serial = seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return serial;
|
return serial;
|
@ -17,13 +17,13 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_OGG_SERIAL_HXX
|
#ifndef MPD_SERIAL_HXX
|
||||||
#define MPD_OGG_SERIAL_HXX
|
#define MPD_SERIAL_HXX
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the next pseudo-random Ogg serial.
|
* Generate the next pseudo-random serial.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
GenerateOggSerial() noexcept;
|
GenerateSerial() noexcept;
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -29,6 +29,7 @@ util = static_library(
|
|||||||
'ByteReverse.cxx',
|
'ByteReverse.cxx',
|
||||||
'format.c',
|
'format.c',
|
||||||
'BitReverse.cxx',
|
'BitReverse.cxx',
|
||||||
|
'Serial.cxx',
|
||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user