From 42c4068d2a746300aedb4a3305394305412b3ce8 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 9 Feb 2024 10:49:55 -0500 Subject: [PATCH] add flac decoding to correctly add flac sounds to sound banks --- .gitmodules | 3 + premake5.lua | 2 + src/ObjCommon.lua | 2 + src/ObjCommon/Sound/FlacDecoder.cpp | 77 +++++++++++++++++++ src/ObjCommon/Sound/FlacDecoder.h | 21 +++++ .../SoundBank/SoundBankWriter.cpp | 51 +++++++----- thirdparty/flac | 1 + thirdparty/flac.lua | 47 +++++++++++ 8 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 src/ObjCommon/Sound/FlacDecoder.cpp create mode 100644 src/ObjCommon/Sound/FlacDecoder.h create mode 160000 thirdparty/flac create mode 100644 thirdparty/flac.lua diff --git a/.gitmodules b/.gitmodules index 28b1e92e..241c59ce 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [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 6d9b19f7..2eb1d6d7 100644 --- a/premake5.lua +++ b/premake5.lua @@ -83,6 +83,7 @@ workspace "OpenAssetTools" -- ThirdParty -- ======================== include "thirdparty/catch2.lua" +include "thirdparty/flac.lua" include "thirdparty/libtomcrypt.lua" include "thirdparty/libtommath.lua" include "thirdparty/json.lua" @@ -94,6 +95,7 @@ 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 2d591bbe..19ae27e8 100644 --- a/src/ObjCommon.lua +++ b/src/ObjCommon.lua @@ -15,6 +15,7 @@ function ObjCommon:link(links) links:linkto(Utils) links:linkto(Common) links:linkto(minizip) + links:linkto(flac) end function ObjCommon:use() @@ -48,4 +49,5 @@ 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 new file mode 100644 index 00000000..ccaad5b0 --- /dev/null +++ b/src/ObjCommon/Sound/FlacDecoder.cpp @@ -0,0 +1,77 @@ +#include +#include "FlacDecoder.h" + +class fx_flac_raii +{ +public: + fx_flac_raii() + { + ptr = FX_FLAC_ALLOC_DEFAULT(); + } + + ~fx_flac_raii() + { + free(ptr); + } + + operator fx_flac_t* () + { + 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) + { + 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; + } +}; + +std::unique_ptr FlacDecoder::Create(void* data, size_t length) +{ + return std::make_unique(data, length); +} diff --git a/src/ObjCommon/Sound/FlacDecoder.h b/src/ObjCommon/Sound/FlacDecoder.h new file mode 100644 index 00000000..1d6a46d0 --- /dev/null +++ b/src/ObjCommon/Sound/FlacDecoder.h @@ -0,0 +1,21 @@ +#pragma once +#include + +class FlacDecoder +{ +public: + FlacDecoder() = default; + virtual ~FlacDecoder() = default; + + FlacDecoder(const FlacDecoder& other) = default; + FlacDecoder(FlacDecoder&& other) noexcept = default; + FlacDecoder& operator=(const FlacDecoder& other) = default; + FlacDecoder& operator=(FlacDecoder&& other) noexcept = default; + + 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); +}; \ No newline at end of file diff --git a/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp b/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp index d44035da..1ea92661 100644 --- a/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp +++ b/src/ObjLoading/ObjContainer/SoundBank/SoundBankWriter.cpp @@ -3,9 +3,11 @@ #include "Crypto.h" #include "ObjContainer/SoundBank/SoundBankTypes.h" #include "Sound/WavTypes.h" +#include "Sound/FlacDecoder.h" #include "Utils/Alignment.h" #include "Utils/FileUtils.h" + #include #include #include @@ -128,13 +130,6 @@ public: soundSize = static_cast(wavFile.m_length - sizeof(WavHeader)); auto frameCount = soundSize / (header.formatChunk.nChannels * (header.formatChunk.wBitsPerSample / 8)); - - if (!sound.streamed && header.formatChunk.nSamplesPerSec != 48000) - { - std::cout << "WARNING: \"" << soundFilePath << "\" has a framerate of " << header.formatChunk.nSamplesPerSec - << ". Loaded sounds are recommended to have a framerate of 48000. This sound may not work on all games!" << std::endl; - } - auto frameRateIndex = INDEX_FOR_FRAMERATE[header.formatChunk.nSamplesPerSec]; SoundAssetBankEntry entry{ @@ -161,21 +156,31 @@ public: { soundSize = static_cast(flacFile.m_length); - SoundAssetBankEntry entry{ - soundId, - soundSize, - static_cast(m_current_offset), - 0, - 0, - 0, - 0, - 8, - }; - - m_entries.push_back(entry); - soundData = std::make_unique(soundSize); flacFile.m_stream->read(soundData.get(), soundSize); + + auto decoder = FlacDecoder::Create(soundData.get(), soundSize); + if (decoder->Decode()) + { + auto frameRateIndex = INDEX_FOR_FRAMERATE[decoder->GetFrameRate()]; + SoundAssetBankEntry entry{ + soundId, + soundSize, + static_cast(m_current_offset), + decoder->GetFrameCount(), + frameRateIndex, + decoder->GetNumChannels(), + sound.looping, + 8, + }; + + m_entries.push_back(entry); + } + else + { + std::cerr << "Unable to decode .flac file for sound " << soundFilePath << std::endl; + return false; + } } else { @@ -184,6 +189,12 @@ public: } } + auto lastEntry = m_entries.rbegin(); + if (!sound.streamed && lastEntry->frameRateIndex != 6) + { + std::cout << "WARNING: Loaded sound \"" << soundFilePath << "\" should have a framerate of 48000 but doesn't. This sound may not work on all games!" << std::endl; + } + // calculate checksum SoundAssetBankChecksum checksum{}; diff --git a/thirdparty/flac b/thirdparty/flac new file mode 160000 index 00000000..1b04fafb --- /dev/null +++ b/thirdparty/flac @@ -0,0 +1 @@ +Subproject commit 1b04fafb51aac0c3b7f0118a7bf7c93f6a60d824 diff --git a/thirdparty/flac.lua b/thirdparty/flac.lua new file mode 100644 index 00000000..b840aa9d --- /dev/null +++ b/thirdparty/flac.lua @@ -0,0 +1,47 @@ +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