mirror of https://github.com/OpenRCT2/OpenRCT2.git
Add ogg vorbis support
This commit is contained in:
parent
73ac7954f1
commit
274bd921b3
|
@ -15,7 +15,7 @@ else ()
|
||||||
PKG_CHECK_MODULES(SDL2 REQUIRED IMPORTED_TARGET sdl2)
|
PKG_CHECK_MODULES(SDL2 REQUIRED IMPORTED_TARGET sdl2)
|
||||||
PKG_CHECK_MODULES(SPEEX REQUIRED IMPORTED_TARGET speexdsp)
|
PKG_CHECK_MODULES(SPEEX REQUIRED IMPORTED_TARGET speexdsp)
|
||||||
PKG_CHECK_MODULES(OGG REQUIRED IMPORTED_TARGET ogg)
|
PKG_CHECK_MODULES(OGG REQUIRED IMPORTED_TARGET ogg)
|
||||||
PKG_CHECK_MODULES(VORBIS REQUIRED IMPORTED_TARGET vorbis)
|
PKG_CHECK_MODULES(VORBISFILE REQUIRED IMPORTED_TARGET vorbisfile)
|
||||||
PKG_CHECK_MODULES(FLAC REQUIRED IMPORTED_TARGET flac)
|
PKG_CHECK_MODULES(FLAC REQUIRED IMPORTED_TARGET flac)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ if (NOT MSVC AND NOT WIN32)
|
||||||
PkgConfig::SDL2
|
PkgConfig::SDL2
|
||||||
PkgConfig::SPEEX
|
PkgConfig::SPEEX
|
||||||
PkgConfig::OGG
|
PkgConfig::OGG
|
||||||
PkgConfig::VORBIS
|
PkgConfig::VORBISFILE
|
||||||
PkgConfig::FLAC)
|
PkgConfig::FLAC)
|
||||||
else ()
|
else ()
|
||||||
target_link_libraries(${PROJECT_NAME} "libopenrct2"
|
target_link_libraries(${PROJECT_NAME} "libopenrct2"
|
||||||
|
|
|
@ -21,6 +21,19 @@
|
||||||
|
|
||||||
namespace OpenRCT2::Audio
|
namespace OpenRCT2::Audio
|
||||||
{
|
{
|
||||||
|
enum class AudioCodecKind
|
||||||
|
{
|
||||||
|
Wav,
|
||||||
|
Ogg,
|
||||||
|
Flac,
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] int32_t ISDLAudioSource::GetBytesPerSecond() const
|
||||||
|
{
|
||||||
|
auto format = GetFormat();
|
||||||
|
return format.GetBytesPerSecond();
|
||||||
|
}
|
||||||
|
|
||||||
class AudioContext final : public IAudioContext
|
class AudioContext final : public IAudioContext
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -90,6 +103,27 @@ namespace OpenRCT2::Audio
|
||||||
return AddSource(AudioSource::CreateMemoryFromCSS1(rw, index, &format));
|
return AddSource(AudioSource::CreateMemoryFromCSS1(rw, index, &format));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static AudioCodecKind GetAudioCodec(SDL_RWops* rw)
|
||||||
|
{
|
||||||
|
constexpr uint32_t MAGIC_FLAC = 0x43614C66;
|
||||||
|
constexpr uint32_t MAGIC_OGG = 0x5367674F;
|
||||||
|
|
||||||
|
auto magic = SDL_ReadLE32(rw);
|
||||||
|
SDL_RWseek(rw, -4, RW_SEEK_CUR);
|
||||||
|
if (magic == MAGIC_FLAC)
|
||||||
|
{
|
||||||
|
return AudioCodecKind::Flac;
|
||||||
|
}
|
||||||
|
else if (magic == MAGIC_OGG)
|
||||||
|
{
|
||||||
|
return AudioCodecKind::Ogg;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return AudioCodecKind::Wav;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IAudioSource* CreateStreamFromWAV(std::unique_ptr<IStream> stream) override
|
IAudioSource* CreateStreamFromWAV(std::unique_ptr<IStream> stream) override
|
||||||
{
|
{
|
||||||
constexpr size_t STREAM_MIN_SIZE = 2 * 1024 * 1024; // 2 MiB
|
constexpr size_t STREAM_MIN_SIZE = 2 * 1024 * 1024; // 2 MiB
|
||||||
|
@ -100,9 +134,21 @@ namespace OpenRCT2::Audio
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return AddSource(
|
auto codec = GetAudioCodec(rw);
|
||||||
loadIntoRAM ? AudioSource::CreateMemoryFromWAV(rw, &_audioMixer->GetFormat())
|
switch (codec)
|
||||||
: AudioSource::CreateStreamFromWAV(rw));
|
{
|
||||||
|
case AudioCodecKind::Wav:
|
||||||
|
return AddSource(
|
||||||
|
loadIntoRAM ? AudioSource::CreateMemoryFromWAV(rw, &_audioMixer->GetFormat())
|
||||||
|
: AudioSource::CreateStreamFromWAV(rw));
|
||||||
|
case AudioCodecKind::Flac:
|
||||||
|
return AddSource(AudioSource::CreateStreamFromFlac(rw));
|
||||||
|
case AudioCodecKind::Ogg:
|
||||||
|
return AddSource(AudioSource::CreateStreamFromOgg(rw));
|
||||||
|
default:
|
||||||
|
log_verbose("Unsupported audio codec");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartTitleMusic() override
|
void StartTitleMusic() override
|
||||||
|
|
|
@ -51,6 +51,8 @@ namespace OpenRCT2::Audio
|
||||||
struct ISDLAudioSource : public IAudioSource
|
struct ISDLAudioSource : public IAudioSource
|
||||||
{
|
{
|
||||||
[[nodiscard]] virtual AudioFormat GetFormat() const abstract;
|
[[nodiscard]] virtual AudioFormat GetFormat() const abstract;
|
||||||
|
|
||||||
|
[[nodiscard]] int32_t GetBytesPerSecond() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ISDLAudioChannel : public IAudioChannel
|
struct ISDLAudioChannel : public IAudioChannel
|
||||||
|
@ -70,6 +72,7 @@ namespace OpenRCT2::Audio
|
||||||
std::unique_ptr<ISDLAudioSource> CreateStreamFromWAV(const std::string& path);
|
std::unique_ptr<ISDLAudioSource> CreateStreamFromWAV(const std::string& path);
|
||||||
std::unique_ptr<ISDLAudioSource> CreateStreamFromWAV(SDL_RWops* rw);
|
std::unique_ptr<ISDLAudioSource> CreateStreamFromWAV(SDL_RWops* rw);
|
||||||
std::unique_ptr<ISDLAudioSource> CreateStreamFromFlac(SDL_RWops* rw);
|
std::unique_ptr<ISDLAudioSource> CreateStreamFromFlac(SDL_RWops* rw);
|
||||||
|
std::unique_ptr<ISDLAudioSource> CreateStreamFromOgg(SDL_RWops* rw);
|
||||||
} // namespace AudioSource
|
} // namespace AudioSource
|
||||||
|
|
||||||
namespace AudioChannel
|
namespace AudioChannel
|
||||||
|
|
|
@ -33,6 +33,11 @@ namespace OpenRCT2::Audio
|
||||||
{
|
{
|
||||||
return BytesPerSample() * channels;
|
return BytesPerSample() * channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int32_t GetBytesPerSecond() const
|
||||||
|
{
|
||||||
|
return BytesPerSample() * channels * freq;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(const AudioFormat& lhs, const AudioFormat& rhs)
|
inline bool operator==(const AudioFormat& lhs, const AudioFormat& rhs)
|
||||||
|
|
|
@ -208,13 +208,6 @@ namespace OpenRCT2::Audio
|
||||||
|
|
||||||
std::unique_ptr<ISDLAudioSource> AudioSource::CreateStreamFromWAV(SDL_RWops* rw)
|
std::unique_ptr<ISDLAudioSource> 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<FileAudioSource>();
|
auto source = std::make_unique<FileAudioSource>();
|
||||||
if (!source->LoadWAV(rw))
|
if (!source->LoadWAV(rw))
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
* 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 <SDL.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <openrct2/audio/AudioSource.h>
|
||||||
|
#include <openrct2/common.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <vorbis/vorbisfile.h>
|
||||||
|
|
||||||
|
namespace OpenRCT2::Audio
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An audio source which decodes a OGG/Vorbis stream.
|
||||||
|
*/
|
||||||
|
class OggAudioSource final : public ISDLAudioSource
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
AudioFormat _format = {};
|
||||||
|
SDL_RWops* _rw = nullptr;
|
||||||
|
bool _released{};
|
||||||
|
|
||||||
|
std::optional<OggVorbis_File> _file;
|
||||||
|
uint64_t _dataLength{};
|
||||||
|
uint32_t _totalSamples{};
|
||||||
|
int32_t _section{};
|
||||||
|
std::vector<uint8_t> _decodeBuffer;
|
||||||
|
size_t _decodeBufferReadOffset{};
|
||||||
|
size_t _currentOffset{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
~OggAudioSource() 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 LoadOgg(SDL_RWops* rw)
|
||||||
|
{
|
||||||
|
_rw = rw;
|
||||||
|
|
||||||
|
ov_callbacks callbacks{};
|
||||||
|
callbacks.read_func = VorbisCallbackRead;
|
||||||
|
callbacks.tell_func = VorbisCallbackTell;
|
||||||
|
callbacks.seek_func = VorbisCallbackSeek;
|
||||||
|
_file.emplace();
|
||||||
|
if (ov_open_callbacks(_rw, &*_file, NULL, 0, callbacks) < 0)
|
||||||
|
{
|
||||||
|
log_verbose("Could not open OGG/Vorbis stream");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vi = ov_info(&*_file, -1);
|
||||||
|
if (vi == nullptr)
|
||||||
|
{
|
||||||
|
log_verbose("Failed to get OGG/Vorbis info");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_format.format = AUDIO_S16LSB;
|
||||||
|
_format.channels = vi->channels;
|
||||||
|
_format.freq = vi->rate;
|
||||||
|
_totalSamples = ov_pcm_total(&*_file, -1);
|
||||||
|
_dataLength = _totalSamples * _format.channels * sizeof(int16_t);
|
||||||
|
_currentOffset = 0;
|
||||||
|
_section = -1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Read(void* dst, uint64_t offset, size_t len) override
|
||||||
|
{
|
||||||
|
if (!_file)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (_currentOffset != offset)
|
||||||
|
{
|
||||||
|
// We have been asked for a new position in the stream
|
||||||
|
auto byteRate = _format.GetByteRate();
|
||||||
|
auto sampleIndex = offset / byteRate;
|
||||||
|
ov_pcm_seek(&*_file, sampleIndex);
|
||||||
|
_currentOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto readLen = static_cast<int64_t>(len);
|
||||||
|
auto dst8 = reinterpret_cast<char*>(dst);
|
||||||
|
int64_t totalBytesRead{};
|
||||||
|
int64_t bytesRead;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int section = _section;
|
||||||
|
bytesRead = ov_read(&*_file, dst8, readLen, SDL_BYTEORDER == SDL_BIG_ENDIAN, 2, 1, §ion);
|
||||||
|
if (_section != section)
|
||||||
|
{
|
||||||
|
_section = section;
|
||||||
|
}
|
||||||
|
assert(bytesRead <= readLen);
|
||||||
|
dst8 += bytesRead;
|
||||||
|
readLen -= bytesRead;
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
} while (bytesRead > 0 && readLen > 0);
|
||||||
|
|
||||||
|
_currentOffset += totalBytesRead;
|
||||||
|
return totalBytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Unload()
|
||||||
|
{
|
||||||
|
if (_file)
|
||||||
|
{
|
||||||
|
ov_clear(&*_file);
|
||||||
|
}
|
||||||
|
if (_rw != nullptr)
|
||||||
|
{
|
||||||
|
SDL_RWclose(_rw);
|
||||||
|
_rw = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t VorbisCallbackRead(void* ptr, size_t size, size_t nmemb, void* datasource)
|
||||||
|
{
|
||||||
|
return SDL_RWread(reinterpret_cast<SDL_RWops*>(datasource), ptr, size, nmemb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int VorbisCallbackSeek(void* datasource, ogg_int64_t offset, int whence)
|
||||||
|
{
|
||||||
|
return (SDL_RWseek(reinterpret_cast<SDL_RWops*>(datasource), offset, whence) < 0) ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long VorbisCallbackTell(void* datasource)
|
||||||
|
{
|
||||||
|
return static_cast<long>(SDL_RWtell(reinterpret_cast<SDL_RWops*>(datasource)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<ISDLAudioSource> AudioSource::CreateStreamFromOgg(SDL_RWops* rw)
|
||||||
|
{
|
||||||
|
auto source = std::make_unique<OggAudioSource>();
|
||||||
|
if (!source->LoadOgg(rw))
|
||||||
|
{
|
||||||
|
source = nullptr;
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
} // namespace OpenRCT2::Audio
|
|
@ -23,6 +23,7 @@ namespace OpenRCT2::Audio
|
||||||
|
|
||||||
virtual void Release() abstract;
|
virtual void Release() abstract;
|
||||||
virtual bool IsReleased() const abstract;
|
virtual bool IsReleased() const abstract;
|
||||||
|
virtual int32_t GetBytesPerSecond() const abstract;
|
||||||
virtual uint64_t GetLength() const abstract;
|
virtual uint64_t GetLength() const abstract;
|
||||||
virtual size_t Read(void* dst, uint64_t offset, size_t len) abstract;
|
virtual size_t Read(void* dst, uint64_t offset, size_t len) abstract;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include "../Context.h"
|
#include "../Context.h"
|
||||||
#include "../OpenRCT2.h"
|
#include "../OpenRCT2.h"
|
||||||
#include "../PlatformEnvironment.h"
|
#include "../PlatformEnvironment.h"
|
||||||
|
#include "../audio/AudioContext.h"
|
||||||
|
#include "../audio/AudioSource.h"
|
||||||
#include "../core/IStream.hpp"
|
#include "../core/IStream.hpp"
|
||||||
#include "../core/Json.hpp"
|
#include "../core/Json.hpp"
|
||||||
#include "../core/Path.hpp"
|
#include "../core/Path.hpp"
|
||||||
|
@ -30,10 +32,30 @@ void MusicObject::Load()
|
||||||
GetStringTable().Sort();
|
GetStringTable().Sort();
|
||||||
NameStringId = language_allocate_object_string(GetName());
|
NameStringId = language_allocate_object_string(GetName());
|
||||||
|
|
||||||
|
auto audioContext = GetContext()->GetAudioContext();
|
||||||
for (auto& track : _tracks)
|
for (auto& track : _tracks)
|
||||||
{
|
{
|
||||||
track.BytesPerTick = DEFAULT_BYTES_PER_TICK;
|
auto stream = track.Asset.GetStream();
|
||||||
track.Size = track.Asset.GetSize();
|
if (stream != nullptr)
|
||||||
|
{
|
||||||
|
auto source = audioContext->CreateStreamFromWAV(std::move(stream));
|
||||||
|
if (source != nullptr)
|
||||||
|
{
|
||||||
|
track.BytesPerTick = source->GetBytesPerSecond() / 40;
|
||||||
|
track.Size = source->GetLength();
|
||||||
|
source->Release();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
track.BytesPerTick = DEFAULT_BYTES_PER_TICK;
|
||||||
|
track.Size = track.Asset.GetSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
track.BytesPerTick = DEFAULT_BYTES_PER_TICK;
|
||||||
|
track.Size = track.Asset.GetSize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue