Commit 87fa6bca authored by John Regan's avatar John Regan

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.
parent c3226a31
...@@ -583,6 +583,10 @@ Encodes into `FLAC <https://xiph.org/flac/>`_ (lossless). ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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) ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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 @@ ...@@ -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: ...@@ -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() ...@@ -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) ...@@ -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() ...@@ -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 @@ ...@@ -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 ...@@ -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 @@ ...@@ -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( ...@@ -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,
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment