From 73ac7954f1e4e20f31b7894c6186bb6b957fe6ae Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 13 May 2022 01:04:55 +0100 Subject: [PATCH] Add flac support --- .vscode/c_cpp_properties.json | 4 +- src/openrct2-ui/CMakeLists.txt | 18 +- src/openrct2-ui/audio/AudioContext.h | 1 + src/openrct2-ui/audio/FileAudioSource.cpp | 7 + src/openrct2-ui/audio/FlacStream.cpp | 340 ++++++++++++++++++++++ 5 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 src/openrct2-ui/audio/FlacStream.cpp diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5ea0747f52..e63d2b5872 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -30,10 +30,10 @@ "name": "Linux", "includePath": [ "/usr/include", + "/usr/include/SDL2", "/usr/local/include", "${workspaceRoot}", - "${workspaceRoot}/src", - "${workspaceRoot}/bin/googletest-distribution-prefix/src/googletest-distribution/googletest/include/gtest" + "${workspaceRoot}/src" ], "defines": [ "__ENABLE_LIGHTFX__", diff --git a/src/openrct2-ui/CMakeLists.txt b/src/openrct2-ui/CMakeLists.txt index 8364cf3aa9..de9defa8da 100644 --- a/src/openrct2-ui/CMakeLists.txt +++ b/src/openrct2-ui/CMakeLists.txt @@ -14,6 +14,9 @@ if (MSVC) else () PKG_CHECK_MODULES(SDL2 REQUIRED IMPORTED_TARGET sdl2) PKG_CHECK_MODULES(SPEEX REQUIRED IMPORTED_TARGET speexdsp) + PKG_CHECK_MODULES(OGG REQUIRED IMPORTED_TARGET ogg) + PKG_CHECK_MODULES(VORBIS REQUIRED IMPORTED_TARGET vorbis) + PKG_CHECK_MODULES(FLAC REQUIRED IMPORTED_TARGET flac) endif () if (NOT DISABLE_OPENGL) @@ -47,11 +50,17 @@ ipo_set_target_properties(${PROJECT_NAME}) if (NOT MSVC AND NOT WIN32) target_link_libraries(${PROJECT_NAME} "libopenrct2" PkgConfig::SDL2 - PkgConfig::SPEEX) + PkgConfig::SPEEX + PkgConfig::OGG + PkgConfig::VORBIS + PkgConfig::FLAC) else () target_link_libraries(${PROJECT_NAME} "libopenrct2" ${SDL2_LDFLAGS} - ${SPEEX_LDFLAGS}) + ${SPEEX_LDFLAGS} + ${OGG_LDFLAGS} + ${VORBIS_LDFLAGS} + ${FLAC_LDFLAGS}) endif () target_link_platform_libraries(${PROJECT_NAME}) @@ -67,7 +76,10 @@ endif () # Includes target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/.." - ${SPEEX_INCLUDE_DIRS}) + ${SPEEX_INCLUDE_DIRS} + ${OGG_INCLUDE_DIRS} + ${VORBIS_INCLUDE_DIRS} + ${FLAC_INCLUDE_DIRS}) target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${SDL2_INCLUDE_DIRS} "${CMAKE_CURRENT_LIST_DIR}/../thirdparty") diff --git a/src/openrct2-ui/audio/AudioContext.h b/src/openrct2-ui/audio/AudioContext.h index a353ec6297..9b96a11a53 100644 --- a/src/openrct2-ui/audio/AudioContext.h +++ b/src/openrct2-ui/audio/AudioContext.h @@ -69,6 +69,7 @@ namespace OpenRCT2::Audio std::unique_ptr CreateMemoryFromWAV(SDL_RWops* rw, const AudioFormat* targetFormat = nullptr); std::unique_ptr CreateStreamFromWAV(const std::string& path); std::unique_ptr CreateStreamFromWAV(SDL_RWops* rw); + std::unique_ptr CreateStreamFromFlac(SDL_RWops* rw); } // namespace AudioSource namespace AudioChannel diff --git a/src/openrct2-ui/audio/FileAudioSource.cpp b/src/openrct2-ui/audio/FileAudioSource.cpp index 685efb7ec7..a2b34aab1c 100644 --- a/src/openrct2-ui/audio/FileAudioSource.cpp +++ b/src/openrct2-ui/audio/FileAudioSource.cpp @@ -208,6 +208,13 @@ namespace OpenRCT2::Audio std::unique_ptr AudioSource::CreateStreamFromWAV(SDL_RWops* rw) { + auto magic = SDL_ReadLE32(rw); + SDL_RWseek(rw, -4, RW_SEEK_CUR); + if (magic == 0x43614C66) + { + return AudioSource::CreateStreamFromFlac(rw); + } + auto source = std::make_unique(); if (!source->LoadWAV(rw)) { diff --git a/src/openrct2-ui/audio/FlacStream.cpp b/src/openrct2-ui/audio/FlacStream.cpp new file mode 100644 index 0000000000..e6beed5e61 --- /dev/null +++ b/src/openrct2-ui/audio/FlacStream.cpp @@ -0,0 +1,340 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "AudioContext.h" +#include "AudioFormat.h" + +#include +#include +#include +#include +#include + +namespace OpenRCT2::Audio +{ + /** + * An audio source which decodes a FLAC stream. + */ + class FlacAudioSource final : public ISDLAudioSource + { + private: + AudioFormat _format = {}; + SDL_RWops* _rw = nullptr; + bool _released{}; + + FLAC__StreamDecoder* _decoder{}; + uint32_t _bitsPerSample{}; + uint32_t _totalSamples{}; + uint64_t _dataLength{}; + std::vector _decodeBuffer; + size_t _decodeBufferReadOffset{}; + size_t _currentOffset{}; + + public: + ~FlacAudioSource() override + { + Release(); + } + + bool IsReleased() const override + { + return _released; + } + + void Release() override + { + if (!_released) + { + Unload(); + _released = true; + } + } + + [[nodiscard]] uint64_t GetLength() const override + { + return _dataLength; + } + + [[nodiscard]] AudioFormat GetFormat() const override + { + return _format; + } + + bool LoadFlac(SDL_RWops* rw) + { + _rw = rw; + _decoder = FLAC__stream_decoder_new(); + if (_decoder == nullptr) + { + log_verbose("Could not create FLAC stream decoder"); + return false; + } + + auto status = FLAC__stream_decoder_init_stream( + _decoder, FlacCallbackRead, FlacCallbackSeek, FlacCallbackTell, FlacCallbackLength, FlacCallbackEof, + FlacCallbackWrite, FlacCallbackMetadata, FlacCallbackError, this); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + log_verbose("Could not initialise FLAC stream"); + return false; + } + + if (!FLAC__stream_decoder_process_until_end_of_metadata(_decoder)) + { + log_verbose("Could not read FLAC metadata"); + return false; + } + + return true; + } + + size_t Read(void* dst, uint64_t offset, size_t len) override + { + if (_decoder == nullptr) + return 0; + + if (_currentOffset != offset) + { + // We have been asked for a new position in the stream + auto byteRate = _format.GetByteRate(); + Seek(offset / byteRate); + _currentOffset = offset; + } + + auto dst8 = reinterpret_cast(dst); + auto bytesRead = ReadFromDecodeBuffer(dst8, len); + dst8 += bytesRead; + if (bytesRead < len) + { + FillDecodeBuffer(len - bytesRead); + bytesRead += ReadFromDecodeBuffer(dst8, len - bytesRead); + dst8 += bytesRead; + } + + _currentOffset += bytesRead; + return bytesRead; + } + + private: + void Seek(uint64_t sampleIndex) + { + ResetDecodeBuffer(); + if (!FLAC__stream_decoder_seek_absolute(_decoder, sampleIndex)) + { + if (FLAC__stream_decoder_get_state(_decoder) == FLAC__STREAM_DECODER_SEEK_ERROR) + { + FLAC__stream_decoder_flush(_decoder); + } + } + } + + void ResetDecodeBuffer() + { + _decodeBufferReadOffset = 0; + _decodeBuffer.clear(); + } + + size_t ReadFromDecodeBuffer(void* dst, size_t len) + { + auto decodeReadLen = std::min(_decodeBuffer.size() - _decodeBufferReadOffset, len); + std::memcpy(dst, _decodeBuffer.data() + _decodeBufferReadOffset, decodeReadLen); + _decodeBufferReadOffset += decodeReadLen; + if (_decodeBufferReadOffset == _decodeBuffer.size()) + { + ResetDecodeBuffer(); + } + return decodeReadLen; + } + + void FillDecodeBuffer(size_t minimumLength) + { + // Decode more data until we have enough to pass back or we have reached the end + while (FLAC__stream_decoder_get_state(_decoder) != FLAC__STREAM_DECODER_END_OF_STREAM + && _decodeBuffer.size() < minimumLength) + { + if (!FLAC__stream_decoder_process_single(_decoder)) + { + break; + } + } + } + + void Unload() + { + if (_decoder != nullptr) + { + FLAC__stream_decoder_delete(_decoder); + } + if (_rw != nullptr) + { + SDL_RWclose(_rw); + _rw = nullptr; + } + } + + static FLAC__StreamDecoderReadStatus FlacCallbackRead( + const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, void* clientData) + { + auto* self = reinterpret_cast(clientData); + if (*bytes > 0) + { + *bytes = SDL_RWread(self->_rw, buffer, sizeof(FLAC__byte), *bytes); + if (*bytes == 0) + { + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } + else + { + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + } + else + { + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + } + + static FLAC__StreamDecoderSeekStatus FlacCallbackSeek( + const FLAC__StreamDecoder* decoder, FLAC__uint64 absoluteByteOffset, void* clientData) + { + auto* self = reinterpret_cast(clientData); + if (SDL_RWseek(self->_rw, absoluteByteOffset, RW_SEEK_SET) < 0) + { + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + } + else + { + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; + } + } + + static FLAC__StreamDecoderTellStatus FlacCallbackTell( + const FLAC__StreamDecoder* decoder, FLAC__uint64* absoluteByteOffset, void* clientData) + { + auto* self = reinterpret_cast(clientData); + auto pos = SDL_RWtell(self->_rw); + if (pos < 0) + { + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + } + else + { + *absoluteByteOffset = static_cast(pos); + return FLAC__STREAM_DECODER_TELL_STATUS_OK; + } + } + + static FLAC__StreamDecoderLengthStatus FlacCallbackLength( + const FLAC__StreamDecoder* decoder, FLAC__uint64* streamLength, void* clientData) + { + auto* self = reinterpret_cast(clientData); + auto pos = SDL_RWtell(self->_rw); + auto length = SDL_RWseek(self->_rw, 0, RW_SEEK_END); + if (SDL_RWseek(self->_rw, pos, RW_SEEK_SET) != pos || length < 0) + { + return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; + } + else + { + *streamLength = static_cast(length); + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } + } + + static FLAC__bool FlacCallbackEof(const FLAC__StreamDecoder* decoder, void* clientData) + { + auto* self = reinterpret_cast(clientData); + auto pos = SDL_RWtell(self->_rw); + auto end = SDL_RWseek(self->_rw, 0, RW_SEEK_END); + if (pos == end) + { + return true; + } + else + { + SDL_RWseek(self->_rw, pos, RW_SEEK_SET); + return false; + } + } + + static FLAC__StreamDecoderWriteStatus FlacCallbackWrite( + const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, const FLAC__int32* const buffer[], void* clientData) + { + auto* self = reinterpret_cast(clientData); + + // Determine sizes + auto channels = self->_format.channels; + auto sampleSize = sizeof(int16_t); + auto frameSize = frame->header.blocksize * channels * sampleSize; + int shiftAmount; + switch (self->_bitsPerSample) + { + case 16: + shiftAmount = 0; + break; + case 20: + shiftAmount = 4; + break; + case 24: + shiftAmount = 8; + break; + default: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + // Allocate room on decode buffer + auto& decodeBuffer = self->_decodeBuffer; + decodeBuffer.resize(decodeBuffer.size() + frameSize); + + // Copy decoded data to buffer + auto dst0 = reinterpret_cast(decodeBuffer.data()); + for (int32_t i = 0; i < channels; i++) + { + auto* dst = dst0 + i; + for (uint32_t j = 0; j < frame->header.blocksize; j++) + { + *dst = static_cast(buffer[i][j] >> shiftAmount); + dst += channels; + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + + static void FlacCallbackMetadata( + const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* clientData) + { + auto* self = reinterpret_cast(clientData); + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) + { + self->_bitsPerSample = metadata->data.stream_info.bits_per_sample; + self->_totalSamples = metadata->data.stream_info.total_samples; + self->_format.freq = metadata->data.stream_info.sample_rate; + self->_format.format = AUDIO_S16LSB; + self->_format.channels = metadata->data.stream_info.channels; + self->_dataLength = self->_totalSamples * self->_format.channels * sizeof(int16_t); + } + } + + static void FlacCallbackError( + const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* clientData) + { + } + }; + + std::unique_ptr AudioSource::CreateStreamFromFlac(SDL_RWops* rw) + { + auto source = std::make_unique(); + if (!source->LoadFlac(rw)) + { + source = nullptr; + } + return source; + } +} // namespace OpenRCT2::Audio