From 87fa6bca544722550424404b19594f465a391374 Mon Sep 17 00:00:00 2001 From: John Regan Date: Sat, 15 May 2021 10:16:49 -0400 Subject: 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. --- src/encoder/plugins/FlacEncoderPlugin.cxx | 97 ++++++++++++++++++++++++----- src/encoder/plugins/OggEncoder.hxx | 4 +- src/encoder/plugins/OpusEncoderPlugin.cxx | 2 +- src/encoder/plugins/VorbisEncoderPlugin.cxx | 2 +- src/lib/xiph/OggSerial.cxx | 46 -------------- src/lib/xiph/OggSerial.hxx | 29 --------- src/lib/xiph/meson.build | 1 - src/util/Serial.cxx | 46 ++++++++++++++ src/util/Serial.hxx | 29 +++++++++ src/util/meson.build | 1 + 10 files changed, 160 insertions(+), 97 deletions(-) delete mode 100644 src/lib/xiph/OggSerial.cxx delete mode 100644 src/lib/xiph/OggSerial.hxx create mode 100644 src/util/Serial.cxx create mode 100644 src/util/Serial.hxx (limited to 'src') diff --git a/src/encoder/plugins/FlacEncoderPlugin.cxx b/src/encoder/plugins/FlacEncoderPlugin.cxx index f2c514178..352434f41 100644 --- a/src/encoder/plugins/FlacEncoderPlugin.cxx +++ b/src/encoder/plugins/FlacEncoderPlugin.cxx @@ -23,8 +23,11 @@ #include "pcm/Buffer.hxx" #include "util/DynamicFifoBuffer.hxx" #include "util/RuntimeError.hxx" +#include "util/Serial.hxx" +#include "util/StringUtil.hxx" #include +#include #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 #error libFLAC is too old @@ -34,6 +37,7 @@ class FlacEncoder final : public Encoder { const AudioFormat audio_format; FLAC__StreamEncoder *const fse; + const unsigned compression; PcmBuffer expand_buffer; @@ -44,7 +48,7 @@ class FlacEncoder final : public Encoder { DynamicFifoBuffer output_buffer; public: - FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse); + FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining); ~FlacEncoder() noexcept override { FLAC__stream_encoder_delete(fse); @@ -56,9 +60,14 @@ public: } void Flush() override { + } + + void PreTag() override { (void) FLAC__stream_encoder_finish(fse); } + void SendTag(const Tag &tag) override; + void Write(const void *data, size_t length) override; size_t Read(void *dest, size_t length) noexcept override { @@ -80,6 +89,8 @@ private: class PreparedFlacEncoder final : public PreparedEncoder { const unsigned compression; + const bool oggchaining; + const bool oggflac; public: explicit PreparedFlacEncoder(const ConfigBlock &block); @@ -88,12 +99,16 @@ public: Encoder *Open(AudioFormat &audio_format) override; [[nodiscard]] const char *GetMimeType() const noexcept override { - return "audio/flac"; + if(oggflac) + return "audio/ogg"; + return "audio/flac"; } }; 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 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)) throw FormatRuntimeError("error setting flac compression to %d", compression); @@ -123,16 +153,26 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, audio_format.sample_rate)) throw FormatRuntimeError("error setting flac sample rate to %d", 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) - :Encoder(false), +FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, unsigned _compression, bool _oggflac, bool _oggchaining) + :Encoder(_oggchaining), audio_format(_audio_format), fse(_fse), + compression(_compression), output_buffer(8192) { /* 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, WriteCallback, nullptr, nullptr, nullptr, @@ -146,24 +186,17 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse) Encoder * PreparedFlacEncoder::Open(AudioFormat &audio_format) { - unsigned bits_per_sample; - - /* FIXME: flac should support 32bit as well */ switch (audio_format.format) { case SampleFormat::S8: - bits_per_sample = 8; break; case SampleFormat::S16: - bits_per_sample = 16; break; case SampleFormat::S24_P32: - bits_per_sample = 24; break; default: - bits_per_sample = 24; audio_format.format = SampleFormat::S24_P32; } @@ -173,16 +206,46 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format) throw std::runtime_error("FLAC__stream_encoder_new() failed"); try { - flac_encoder_setup(fse, compression, - audio_format, bits_per_sample); + flac_encoder_setup(fse, compression, audio_format); } catch (...) { FLAC__stream_encoder_delete(fse); 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 pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples) noexcept { diff --git a/src/encoder/plugins/OggEncoder.hxx b/src/encoder/plugins/OggEncoder.hxx index aa668bcb9..39b62d822 100644 --- a/src/encoder/plugins/OggEncoder.hxx +++ b/src/encoder/plugins/OggEncoder.hxx @@ -23,7 +23,7 @@ #include "../EncoderAPI.hxx" #include "lib/xiph/OggStreamState.hxx" #include "lib/xiph/OggPage.hxx" -#include "lib/xiph/OggSerial.hxx" +#include "util/Serial.hxx" #include @@ -42,7 +42,7 @@ protected: public: OggEncoder(bool _implements_tag) :Encoder(_implements_tag), - stream(GenerateOggSerial()) { + stream(GenerateSerial()) { } /* virtual methods from class Encoder */ diff --git a/src/encoder/plugins/OpusEncoderPlugin.cxx b/src/encoder/plugins/OpusEncoderPlugin.cxx index d579d1e50..717e5f998 100644 --- a/src/encoder/plugins/OpusEncoderPlugin.cxx +++ b/src/encoder/plugins/OpusEncoderPlugin.cxx @@ -405,7 +405,7 @@ OpusEncoder::PreTag() void OpusEncoder::SendTag(const Tag &tag) { - stream.Reinitialize(GenerateOggSerial()); + stream.Reinitialize(GenerateSerial()); opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&lookahead)); GenerateHeaders(&tag); } diff --git a/src/encoder/plugins/VorbisEncoderPlugin.cxx b/src/encoder/plugins/VorbisEncoderPlugin.cxx index 4acf1ec05..c460ab94e 100644 --- a/src/encoder/plugins/VorbisEncoderPlugin.cxx +++ b/src/encoder/plugins/VorbisEncoderPlugin.cxx @@ -225,7 +225,7 @@ VorbisEncoder::SendTag(const Tag &tag) /* reset ogg_stream_state and begin a new stream */ - stream.Reinitialize(GenerateOggSerial()); + stream.Reinitialize(GenerateSerial()); /* send that vorbis_comment to the ogg_stream_state */ diff --git a/src/lib/xiph/OggSerial.cxx b/src/lib/xiph/OggSerial.cxx deleted file mode 100644 index 6f5ba3f84..000000000 --- a/src/lib/xiph/OggSerial.cxx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2003-2021 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "OggSerial.hxx" -#include "util/Compiler.h" - -#include -#include - -static std::atomic_uint next_ogg_serial; - -int -GenerateOggSerial() noexcept -{ - unsigned serial = ++next_ogg_serial; - if (gcc_unlikely(serial < 16)) { - /* first-time initialization: seed with a clock value, - which is random enough for our use */ - - /* this code is not race-free, but good enough */ - using namespace std::chrono; - const auto now = steady_clock::now().time_since_epoch(); - const auto now_ms = duration_cast(now); - const unsigned seed = now_ms.count(); - next_ogg_serial = serial = seed; - } - - return serial; -} - diff --git a/src/lib/xiph/OggSerial.hxx b/src/lib/xiph/OggSerial.hxx deleted file mode 100644 index bc6298139..000000000 --- a/src/lib/xiph/OggSerial.hxx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2003-2021 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OGG_SERIAL_HXX -#define MPD_OGG_SERIAL_HXX - -/** - * Generate the next pseudo-random Ogg serial. - */ -int -GenerateOggSerial() noexcept; - -#endif diff --git a/src/lib/xiph/meson.build b/src/lib/xiph/meson.build index d57d68d8f..4666d28fc 100644 --- a/src/lib/xiph/meson.build +++ b/src/lib/xiph/meson.build @@ -72,7 +72,6 @@ if libogg_dep.found() ogg = static_library( 'ogg', 'OggVisitor.cxx', - 'OggSerial.cxx', 'OggSyncState.cxx', 'OggFind.cxx', 'OggPacket.cxx', diff --git a/src/util/Serial.cxx b/src/util/Serial.cxx new file mode 100644 index 000000000..c91fe62da --- /dev/null +++ b/src/util/Serial.cxx @@ -0,0 +1,46 @@ +/* + * Copyright 2003-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Serial.hxx" +#include "Compiler.h" + +#include +#include + +static std::atomic_uint next_serial; + +int +GenerateSerial() noexcept +{ + unsigned serial = ++next_serial; + if (gcc_unlikely(serial < 16)) { + /* first-time initialization: seed with a clock value, + which is random enough for our use */ + + /* this code is not race-free, but good enough */ + using namespace std::chrono; + const auto now = steady_clock::now().time_since_epoch(); + const auto now_ms = duration_cast(now); + const unsigned seed = now_ms.count(); + next_serial = serial = seed; + } + + return serial; +} + diff --git a/src/util/Serial.hxx b/src/util/Serial.hxx new file mode 100644 index 000000000..2e8723e7f --- /dev/null +++ b/src/util/Serial.hxx @@ -0,0 +1,29 @@ +/* + * Copyright 2003-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SERIAL_HXX +#define MPD_SERIAL_HXX + +/** + * Generate the next pseudo-random serial. + */ +int +GenerateSerial() noexcept; + +#endif diff --git a/src/util/meson.build b/src/util/meson.build index dc37588cb..953296e5c 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -29,6 +29,7 @@ util = static_library( 'ByteReverse.cxx', 'format.c', 'BitReverse.cxx', + 'Serial.cxx', include_directories: inc, ) -- cgit v1.2.3