Add ogg vorbis support

This commit is contained in:
Ted John 2022-05-14 03:05:03 +01:00
parent 73ac7954f1
commit 274bd921b3
8 changed files with 261 additions and 14 deletions

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))
{

View File

@ -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, &section);
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

View File

@ -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;
};

View File

@ -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();
}
}
}