From 4591787989fdf1c68ebc320d77d1a421b0f2a663 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 10 Feb 2024 16:33:26 +0100 Subject: [PATCH] chore: drop third party lib for reading flac header --- .gitmodules | 3 - premake5.lua | 2 - src/ObjCommon.lua | 2 - src/ObjCommon/Sound/FlacDecoder.cpp | 294 ++++++++++++++---- src/ObjCommon/Sound/FlacDecoder.h | 36 ++- .../SoundBank/SoundBankWriter.cpp | 10 +- test/ObjCommonTests/Sound/FlacDecoderTest.cpp | 39 +++ thirdparty/flac | 1 - thirdparty/flac.lua | 47 --- 9 files changed, 295 insertions(+), 139 deletions(-) create mode 100644 test/ObjCommonTests/Sound/FlacDecoderTest.cpp delete mode 160000 thirdparty/flac delete mode 100644 thirdparty/flac.lua diff --git a/.gitmodules b/.gitmodules index 241c59ce..28b1e92e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,3 @@ [submodule "thirdparty/json"] path = thirdparty/json url = https://github.com/nlohmann/json.git -[submodule "thirdparty/flac"] - path = thirdparty/flac - url = https://github.com/astoeckel/libfoxenflac.git diff --git a/premake5.lua b/premake5.lua index 2eb1d6d7..6d9b19f7 100644 --- a/premake5.lua +++ b/premake5.lua @@ -83,7 +83,6 @@ workspace "OpenAssetTools" -- ThirdParty -- ======================== include "thirdparty/catch2.lua" -include "thirdparty/flac.lua" include "thirdparty/libtomcrypt.lua" include "thirdparty/libtommath.lua" include "thirdparty/json.lua" @@ -95,7 +94,6 @@ include "thirdparty/zlib.lua" -- ThirdParty group: All projects that are external dependencies group "ThirdParty" catch2:project() - flac:project() libtommath:project() libtomcrypt:project() json:project() diff --git a/src/ObjCommon.lua b/src/ObjCommon.lua index 19ae27e8..2d591bbe 100644 --- a/src/ObjCommon.lua +++ b/src/ObjCommon.lua @@ -15,7 +15,6 @@ function ObjCommon:link(links) links:linkto(Utils) links:linkto(Common) links:linkto(minizip) - links:linkto(flac) end function ObjCommon:use() @@ -49,5 +48,4 @@ function ObjCommon:project() self:include(includes) Utils:include(includes) - flac:include(includes) end diff --git a/src/ObjCommon/Sound/FlacDecoder.cpp b/src/ObjCommon/Sound/FlacDecoder.cpp index 335a8b70..cff3072f 100644 --- a/src/ObjCommon/Sound/FlacDecoder.cpp +++ b/src/ObjCommon/Sound/FlacDecoder.cpp @@ -1,77 +1,245 @@ #include "FlacDecoder.h" -#include +#include "Utils/Alignment.h" +#include "Utils/Endianness.h" +#include "Utils/FileUtils.h" -class fx_flac_raii +#include +#include +#include + +namespace { -public: - fx_flac_raii() + constexpr auto FLAC_MAGIC = FileUtils::MakeMagic32('f', 'L', 'a', 'C'); + + enum class MetaDataBlockType : unsigned { - ptr = FX_FLAC_ALLOC_DEFAULT(); - } + STREAMINFO = 0, + PADDING = 1, + APPLICATION = 2, + SEEKTABLE = 3, + VORBIS_COMMENT = 4, + CUESHEET = 5, + PICTURE = 6 + }; - ~fx_flac_raii() + struct MetaDataBlockHeader { - free(ptr); - } + uint8_t isLastMetaDataBlock; + MetaDataBlockType blockType; + uint32_t blockLength; + }; - operator fx_flac_t*() + constexpr auto STREAM_INFO_BLOCK_SIZE = 34; + + class FlacReadingException final : public std::exception { - return ptr; - } - -private: - fx_flac_t* ptr; -}; - -class FlacDecoderImpl : public FlacDecoder -{ -private: - void* m_data; - size_t m_length; - std::unique_ptr m_flac; - -public: - explicit FlacDecoderImpl(void* data, size_t length) - : m_data(data), - m_length(length) - { - } - - unsigned int GetFrameCount() override - { - return static_cast(fx_flac_get_streaminfo(*m_flac.get(), FLAC_KEY_N_SAMPLES)); - } - - unsigned int GetFrameRate() override - { - return static_cast(fx_flac_get_streaminfo(*m_flac.get(), FLAC_KEY_SAMPLE_RATE)); - } - - unsigned int GetNumChannels() override - { - return static_cast(fx_flac_get_streaminfo(*m_flac.get(), FLAC_KEY_N_CHANNELS)); - } - - bool Decode() override - { - m_flac = std::make_unique(); - - while (true) + public: + explicit FlacReadingException(std::string message) + : m_message(std::move(message)) { - auto res = fx_flac_process(*m_flac.get(), reinterpret_cast(m_data), &m_length, nullptr, nullptr); - if (res == FLAC_ERR) - return false; - - if (res == FLAC_END_OF_METADATA) - break; } - return true; - } -}; + _NODISCARD char const* what() const noexcept override + { + return m_message.c_str(); + } -std::unique_ptr FlacDecoder::Create(void* data, size_t length) + private: + std::string m_message; + }; + + class FlacBitReader + { + public: + explicit FlacBitReader(std::istream& stream) + : m_stream(stream), + m_last_byte(0u), + m_remaining_bits_last_byte(0u) + { + } + + template T ReadBits(const size_t bitCount) + { + union + { + uint8_t buffer[sizeof(T)]; + T result; + } data{}; + + const auto byteCount = utils::Align(bitCount, 8u) / 8u; + assert(byteCount <= sizeof(T)); + + const auto shiftCount = (8u - bitCount % 8) % 8; + + auto remainingBits = bitCount; + +#if HOST_ENDIANNESS == LITTLE_ENDIAN_ENDIANNESS + auto offset = byteCount - 1; +#else + auto offset = 0u; +#endif + + while (remainingBits > 0) + { + const auto curBits = static_cast(std::min(remainingBits, 8u)); + + if (m_remaining_bits_last_byte > 0) + { + if (m_remaining_bits_last_byte < curBits) + { + const auto bitsFromFirstByte = m_remaining_bits_last_byte; + data.buffer[offset] = static_cast(m_last_byte << (8u - bitsFromFirstByte)); + + m_stream.read(reinterpret_cast(&m_last_byte), sizeof(m_last_byte)); + if (m_stream.gcount() != sizeof(m_last_byte)) + throw FlacReadingException("Unexpected eof"); + + const auto bitsFromSecondByte = static_cast(curBits - m_remaining_bits_last_byte); + m_remaining_bits_last_byte = 8u - bitsFromSecondByte; + const auto maskForSecondByte = static_cast(0xFF << (8u - bitsFromSecondByte)); + data.buffer[offset] |= (m_last_byte & maskForSecondByte) >> bitsFromFirstByte; + } + else if (m_remaining_bits_last_byte == curBits) + { + data.buffer[offset] = static_cast(m_last_byte << (8u - curBits)); + m_remaining_bits_last_byte = 0u; + } + else // m_remaining_bits_last_byte > curBits + { + const auto maskForCurBits = 0xFF >> (8u - curBits); + const auto maskForCurBitsInRemainingBits = static_cast(maskForCurBits << (m_remaining_bits_last_byte - curBits)); + const auto selectedData = static_cast(m_last_byte & maskForCurBitsInRemainingBits); + data.buffer[offset] = static_cast(selectedData << (8u - m_remaining_bits_last_byte)); + m_remaining_bits_last_byte -= curBits; + } + } + else if (curBits >= 8u) + { + m_stream.read(reinterpret_cast(&data.buffer[offset]), sizeof(uint8_t)); + if (m_stream.gcount() != sizeof(uint8_t)) + throw FlacReadingException("Unexpected eof"); + } + else + { + m_stream.read(reinterpret_cast(&m_last_byte), sizeof(m_last_byte)); + if (m_stream.gcount() != sizeof(m_last_byte)) + throw FlacReadingException("Unexpected eof"); + + data.buffer[offset] = m_last_byte & (0xFF << (8u - curBits)); + m_remaining_bits_last_byte = static_cast(8u - curBits); + } + + remainingBits -= curBits; +#if HOST_ENDIANNESS == LITTLE_ENDIAN_ENDIANNESS + --offset; +#else + ++offset; +#endif + } + + data.result >>= shiftCount; + return data.result; + } + + void ReadBuffer(void* buffer, const size_t bitCount) + { + assert(m_remaining_bits_last_byte == 0); + assert(bitCount % 8 == 0); + + m_remaining_bits_last_byte = 0; + m_stream.read(static_cast(buffer), bitCount / 8); + } + + void Seek(const size_t offset) + { + assert(m_remaining_bits_last_byte == 0); + m_remaining_bits_last_byte = 0; + m_stream.seekg(offset, std::ios::cur); + } + + private: + std::istream& m_stream; + uint8_t m_last_byte; + uint8_t m_remaining_bits_last_byte; + }; +} // namespace + +namespace flac { - return std::make_unique(data, length); -} + FlacMetaData::FlacMetaData() + : m_minimum_block_size(), + m_maximum_block_size(), + m_minimum_frame_size(), + m_maximum_frame_size(), + m_sample_rate(), + m_number_of_channels(), + m_bits_per_sample(), + m_total_samples(), + m_md5_signature{} + { + } + + void FlacReadStreamInfo(FlacBitReader& reader, FlacMetaData& metaData) + { + metaData.m_minimum_block_size = reader.ReadBits(16); + metaData.m_maximum_block_size = reader.ReadBits(16); + metaData.m_minimum_frame_size = reader.ReadBits(24); + metaData.m_maximum_frame_size = reader.ReadBits(24); + metaData.m_sample_rate = reader.ReadBits(20); + metaData.m_number_of_channels = static_cast(reader.ReadBits(3) + 1); + metaData.m_bits_per_sample = static_cast(reader.ReadBits(5) + 1); + metaData.m_total_samples = reader.ReadBits(36); + reader.ReadBuffer(metaData.m_md5_signature, 128); + } + + bool GetFlacMetaData(std::istream& stream, FlacMetaData& metaData) + { + try + { + uint32_t readMagic; + stream.read(reinterpret_cast(&readMagic), sizeof(readMagic)); + if (stream.gcount() != sizeof(readMagic) || readMagic != FLAC_MAGIC) + throw FlacReadingException("Invalid flac magic"); + + FlacBitReader reader(stream); + while (true) + { + const MetaDataBlockHeader header{ + + reader.ReadBits(1), + static_cast(reader.ReadBits(7)), + reader.ReadBits(24), + }; + + if (header.blockType == MetaDataBlockType::STREAMINFO) + { + if (header.blockLength != STREAM_INFO_BLOCK_SIZE) + throw FlacReadingException("Flac stream info block size invalid"); + + FlacReadStreamInfo(reader, metaData); + return true; + } + + reader.Seek(header.blockLength * 8u); + + if (header.isLastMetaDataBlock) + break; + } + + throw FlacReadingException("Missing flac stream info block"); + } + catch (const FlacReadingException& e) + { + std::cerr << e.what() << "\n"; + } + + return false; + } + + bool GetFlacMetaData(const void* data, const size_t dataSize, FlacMetaData& metaData) + { + std::istringstream ss(std::string(static_cast(data), dataSize)); + return GetFlacMetaData(ss, metaData); + } +} // namespace flac diff --git a/src/ObjCommon/Sound/FlacDecoder.h b/src/ObjCommon/Sound/FlacDecoder.h index 0b659b23..cec71975 100644 --- a/src/ObjCommon/Sound/FlacDecoder.h +++ b/src/ObjCommon/Sound/FlacDecoder.h @@ -1,22 +1,26 @@ #pragma once + +#include #include -#include -class FlacDecoder +namespace flac { -public: - FlacDecoder() = default; - virtual ~FlacDecoder() = default; + class FlacMetaData + { + public: + uint16_t m_minimum_block_size; + uint16_t m_maximum_block_size; + uint32_t m_minimum_frame_size; + uint32_t m_maximum_frame_size; + uint32_t m_sample_rate; + uint8_t m_number_of_channels; + uint8_t m_bits_per_sample; + uint64_t m_total_samples; + uint8_t m_md5_signature[16]; - FlacDecoder(const FlacDecoder& other) = default; - FlacDecoder(FlacDecoder&& other) noexcept = default; - FlacDecoder& operator=(const FlacDecoder& other) = default; - FlacDecoder& operator=(FlacDecoder&& other) noexcept = default; + FlacMetaData(); + }; - virtual bool Decode() = 0; - virtual unsigned int GetFrameCount() = 0; - virtual unsigned int GetFrameRate() = 0; - virtual unsigned int GetNumChannels() = 0; - - static std::unique_ptr Create(void* data, size_t length); -}; + bool GetFlacMetaData(std::istream& stream, FlacMetaData& metaData); + bool GetFlacMetaData(const void* data, size_t dataSize, FlacMetaData& metaData); +} // namespace flac diff --git a/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp b/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp index 77a23362..153a75ba 100644 --- a/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp +++ b/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp @@ -159,17 +159,17 @@ public: soundData = std::make_unique(soundSize); flacFile.m_stream->read(soundData.get(), soundSize); - const auto decoder = FlacDecoder::Create(soundData.get(), soundSize); - if (decoder->Decode()) + flac::FlacMetaData metaData; + if (flac::GetFlacMetaData(soundData.get(), soundSize, metaData)) { - const auto frameRateIndex = INDEX_FOR_FRAMERATE[decoder->GetFrameRate()]; + const auto frameRateIndex = INDEX_FOR_FRAMERATE[metaData.m_sample_rate]; SoundAssetBankEntry entry{ soundId, soundSize, static_cast(m_current_offset), - decoder->GetFrameCount(), + static_cast(metaData.m_total_samples), frameRateIndex, - static_cast(decoder->GetNumChannels()), + metaData.m_number_of_channels, sound.m_looping, 8, }; diff --git a/test/ObjCommonTests/Sound/FlacDecoderTest.cpp b/test/ObjCommonTests/Sound/FlacDecoderTest.cpp new file mode 100644 index 00000000..b7e6ecd4 --- /dev/null +++ b/test/ObjCommonTests/Sound/FlacDecoderTest.cpp @@ -0,0 +1,39 @@ +#include "Sound/FlacDecoder.h" + +#include +#include + +namespace flac +{ + TEST_CASE("FlacDecoder: Ensure properly decodes flac stream info", "[sound][flac]") + { + // clang-format off + constexpr uint8_t testData[] + { + // Magic + 'f', 'L', 'a', 'C', + + // Block header + 0x00, 0x00, 0x00, 0x22, + + // StreamInfo block + 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xB8, 0x02, 0xF0, 0x00, + 0x02, 0xF9, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + // clang-format on + + FlacMetaData metaData; + const auto result = GetFlacMetaData(testData, sizeof(testData), metaData); + + REQUIRE(result == true); + REQUIRE(metaData.m_minimum_block_size == 1024); + REQUIRE(metaData.m_maximum_block_size == 1024); + REQUIRE(metaData.m_minimum_frame_size == 0); + REQUIRE(metaData.m_maximum_frame_size == 0); + REQUIRE(metaData.m_sample_rate == 48000); + REQUIRE(metaData.m_number_of_channels == 2); + REQUIRE(metaData.m_bits_per_sample == 16); + REQUIRE(metaData.m_total_samples == 194870); + } +} // namespace flac diff --git a/thirdparty/flac b/thirdparty/flac deleted file mode 160000 index 1b04fafb..00000000 --- a/thirdparty/flac +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1b04fafb51aac0c3b7f0118a7bf7c93f6a60d824 diff --git a/thirdparty/flac.lua b/thirdparty/flac.lua deleted file mode 100644 index b840aa9d..00000000 --- a/thirdparty/flac.lua +++ /dev/null @@ -1,47 +0,0 @@ -flac = {} - -function flac:include(includes) - if includes:handle(self:name()) then - includedirs { - path.join(ThirdPartyFolder(), "flac") - } - end -end - -function flac:link(links) - links:add(self:name()) -end - -function flac:use() - -end - -function flac:name() - return "flac" -end - -function flac:project() - local folder = ThirdPartyFolder() - local includes = Includes:create() - - project(self:name()) - targetdir(TargetDirectoryLib) - location "%{wks.location}/thirdparty/%{prj.name}" - kind "StaticLib" - language "C" - - files { - path.join(folder, "flac/foxen/*.h"), - path.join(folder, "flac/foxen/*.c") - } - - defines { - "_CRT_SECURE_NO_WARNINGS", - "_CRT_NONSTDC_NO_DEPRECATE" - } - - self:include(includes) - - -- Disable warnings. They do not have any value to us since it is not our code. - warnings "off" -end