mirror of https://github.com/OpenRCT2/OpenRCT2.git
169 lines
4.9 KiB
C++
169 lines
4.9 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2024 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 "SDLAudioSource.h"
|
|
|
|
#include <stdexcept>
|
|
|
|
#ifndef DISABLE_VORBIS
|
|
# include <SDL.h>
|
|
# include <optional>
|
|
# include <vector>
|
|
# include <vorbis/vorbisfile.h>
|
|
#endif
|
|
|
|
namespace OpenRCT2::Audio
|
|
{
|
|
#ifndef DISABLE_VORBIS
|
|
/**
|
|
* An audio source which decodes a OGG/Vorbis stream.
|
|
*/
|
|
class OggAudioSource final : public SDLAudioSource
|
|
{
|
|
private:
|
|
AudioFormat _format = {};
|
|
SDL_RWops* _rw = nullptr;
|
|
|
|
std::optional<OggVorbis_File> _file;
|
|
uint64_t _dataLength{};
|
|
uint32_t _totalSamples{};
|
|
int32_t _section{};
|
|
size_t _currentOffset{};
|
|
|
|
public:
|
|
~OggAudioSource() override
|
|
{
|
|
Release();
|
|
}
|
|
|
|
[[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;
|
|
}
|
|
|
|
protected:
|
|
void Unload() override
|
|
{
|
|
if (_file)
|
|
{
|
|
ov_clear(&*_file);
|
|
}
|
|
if (_rw != nullptr)
|
|
{
|
|
SDL_RWclose(_rw);
|
|
_rw = nullptr;
|
|
}
|
|
}
|
|
|
|
private:
|
|
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)));
|
|
}
|
|
};
|
|
#endif
|
|
|
|
std::unique_ptr<SDLAudioSource> CreateOggAudioSource(SDL_RWops* rw)
|
|
{
|
|
#ifndef DISABLE_VORBIS
|
|
auto source = std::make_unique<OggAudioSource>();
|
|
if (!source->LoadOgg(rw))
|
|
{
|
|
throw std::runtime_error("Unable to load OGG/vorbis stream");
|
|
}
|
|
return source;
|
|
#else
|
|
throw std::runtime_error("OpenRCT2 has not been compiled with OGG/vorbis support");
|
|
#endif
|
|
}
|
|
} // namespace OpenRCT2::Audio
|