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(SPEEX REQUIRED IMPORTED_TARGET speexdsp)
|
||||
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)
|
||||
endif ()
|
||||
|
||||
|
@ -52,7 +52,7 @@ if (NOT MSVC AND NOT WIN32)
|
|||
PkgConfig::SDL2
|
||||
PkgConfig::SPEEX
|
||||
PkgConfig::OGG
|
||||
PkgConfig::VORBIS
|
||||
PkgConfig::VORBISFILE
|
||||
PkgConfig::FLAC)
|
||||
else ()
|
||||
target_link_libraries(${PROJECT_NAME} "libopenrct2"
|
||||
|
|
|
@ -21,6 +21,19 @@
|
|||
|
||||
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
|
||||
{
|
||||
private:
|
||||
|
@ -90,6 +103,27 @@ namespace OpenRCT2::Audio
|
|||
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
|
||||
{
|
||||
constexpr size_t STREAM_MIN_SIZE = 2 * 1024 * 1024; // 2 MiB
|
||||
|
@ -100,9 +134,21 @@ namespace OpenRCT2::Audio
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
return AddSource(
|
||||
loadIntoRAM ? AudioSource::CreateMemoryFromWAV(rw, &_audioMixer->GetFormat())
|
||||
: AudioSource::CreateStreamFromWAV(rw));
|
||||
auto codec = GetAudioCodec(rw);
|
||||
switch (codec)
|
||||
{
|
||||
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
|
||||
|
|
|
@ -51,6 +51,8 @@ namespace OpenRCT2::Audio
|
|||
struct ISDLAudioSource : public IAudioSource
|
||||
{
|
||||
[[nodiscard]] virtual AudioFormat GetFormat() const abstract;
|
||||
|
||||
[[nodiscard]] int32_t GetBytesPerSecond() const override;
|
||||
};
|
||||
|
||||
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(SDL_RWops* rw);
|
||||
std::unique_ptr<ISDLAudioSource> CreateStreamFromFlac(SDL_RWops* rw);
|
||||
std::unique_ptr<ISDLAudioSource> CreateStreamFromOgg(SDL_RWops* rw);
|
||||
} // namespace AudioSource
|
||||
|
||||
namespace AudioChannel
|
||||
|
|
|
@ -33,6 +33,11 @@ namespace OpenRCT2::Audio
|
|||
{
|
||||
return BytesPerSample() * channels;
|
||||
}
|
||||
|
||||
[[nodiscard]] int32_t GetBytesPerSecond() const
|
||||
{
|
||||
return BytesPerSample() * channels * freq;
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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>();
|
||||
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 bool IsReleased() const abstract;
|
||||
virtual int32_t GetBytesPerSecond() const abstract;
|
||||
virtual uint64_t GetLength() const abstract;
|
||||
virtual size_t Read(void* dst, uint64_t offset, size_t len) abstract;
|
||||
};
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "../Context.h"
|
||||
#include "../OpenRCT2.h"
|
||||
#include "../PlatformEnvironment.h"
|
||||
#include "../audio/AudioContext.h"
|
||||
#include "../audio/AudioSource.h"
|
||||
#include "../core/IStream.hpp"
|
||||
#include "../core/Json.hpp"
|
||||
#include "../core/Path.hpp"
|
||||
|
@ -30,10 +32,30 @@ void MusicObject::Load()
|
|||
GetStringTable().Sort();
|
||||
NameStringId = language_allocate_object_string(GetName());
|
||||
|
||||
auto audioContext = GetContext()->GetAudioContext();
|
||||
for (auto& track : _tracks)
|
||||
{
|
||||
track.BytesPerTick = DEFAULT_BYTES_PER_TICK;
|
||||
track.Size = track.Asset.GetSize();
|
||||
auto stream = track.Asset.GetStream();
|
||||
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