mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-18 04:23:20 +01:00
236 lines
7.1 KiB
C++
236 lines
7.1 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2020 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>
|
|
|
|
namespace OpenRCT2::Audio
|
|
{
|
|
/**
|
|
* An audio source where raw PCM data is streamed directly from
|
|
* a file.
|
|
*/
|
|
class FileAudioSource final : public ISDLAudioSource
|
|
{
|
|
private:
|
|
AudioFormat _format = {};
|
|
SDL_RWops* _rw = nullptr;
|
|
uint64_t _dataBegin = 0;
|
|
uint64_t _dataLength = 0;
|
|
|
|
public:
|
|
~FileAudioSource() override
|
|
{
|
|
Unload();
|
|
}
|
|
|
|
[[nodiscard]] uint64_t GetLength() const override
|
|
{
|
|
return _dataLength;
|
|
}
|
|
|
|
[[nodiscard]] AudioFormat GetFormat() const override
|
|
{
|
|
return _format;
|
|
}
|
|
|
|
size_t Read(void* dst, uint64_t offset, size_t len) override
|
|
{
|
|
size_t bytesRead = 0;
|
|
int64_t currentPosition = SDL_RWtell(_rw);
|
|
if (currentPosition != -1)
|
|
{
|
|
size_t bytesToRead = static_cast<size_t>(std::min<uint64_t>(len, _dataLength - offset));
|
|
int64_t dataOffset = _dataBegin + offset;
|
|
if (currentPosition != dataOffset)
|
|
{
|
|
int64_t newPosition = SDL_RWseek(_rw, dataOffset, SEEK_SET);
|
|
if (newPosition == -1)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
bytesRead = SDL_RWread(_rw, dst, 1, bytesToRead);
|
|
}
|
|
return bytesRead;
|
|
}
|
|
|
|
bool LoadWAV(SDL_RWops* rw)
|
|
{
|
|
constexpr uint32_t DATA = 0x61746164;
|
|
constexpr uint32_t FMT = 0x20746D66;
|
|
constexpr uint32_t RIFF = 0x46464952;
|
|
constexpr uint32_t WAVE = 0x45564157;
|
|
constexpr uint16_t pcmformat = 0x0001;
|
|
|
|
Unload();
|
|
|
|
if (rw == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
_rw = rw;
|
|
|
|
uint32_t chunkId = SDL_ReadLE32(rw);
|
|
if (chunkId != RIFF)
|
|
{
|
|
log_verbose("Not a WAV file");
|
|
return false;
|
|
}
|
|
|
|
// Read and discard chunk size
|
|
SDL_ReadLE32(rw);
|
|
uint32_t chunkFormat = SDL_ReadLE32(rw);
|
|
if (chunkFormat != WAVE)
|
|
{
|
|
log_verbose("Not in WAVE format");
|
|
return false;
|
|
}
|
|
|
|
uint32_t fmtChunkSize = FindChunk(rw, FMT);
|
|
if (!fmtChunkSize)
|
|
{
|
|
log_verbose("Could not find FMT chunk");
|
|
return false;
|
|
}
|
|
|
|
uint64_t chunkStart = SDL_RWtell(rw);
|
|
|
|
WaveFormat waveFormat{};
|
|
SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1);
|
|
SDL_RWseek(rw, chunkStart + fmtChunkSize, RW_SEEK_SET);
|
|
if (waveFormat.encoding != pcmformat)
|
|
{
|
|
log_verbose("Not in proper format");
|
|
return false;
|
|
}
|
|
|
|
_format.freq = waveFormat.frequency;
|
|
switch (waveFormat.bitspersample)
|
|
{
|
|
case 8:
|
|
_format.format = AUDIO_U8;
|
|
break;
|
|
case 16:
|
|
_format.format = AUDIO_S16LSB;
|
|
break;
|
|
default:
|
|
log_verbose("Invalid bits per sample");
|
|
return false;
|
|
}
|
|
_format.channels = waveFormat.channels;
|
|
|
|
uint32_t dataChunkSize = FindChunk(rw, DATA);
|
|
if (dataChunkSize == 0)
|
|
{
|
|
log_verbose("Could not find DATA chunk");
|
|
return false;
|
|
}
|
|
|
|
_dataLength = dataChunkSize;
|
|
_dataBegin = SDL_RWtell(rw);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
static uint32_t FindChunk(SDL_RWops* rw, uint32_t wantedId)
|
|
{
|
|
uint32_t subchunkId = SDL_ReadLE32(rw);
|
|
uint32_t subchunkSize = SDL_ReadLE32(rw);
|
|
if (subchunkId == wantedId)
|
|
{
|
|
return subchunkSize;
|
|
}
|
|
constexpr uint32_t FACT = 0x74636166;
|
|
constexpr uint32_t LIST = 0x5453494c;
|
|
constexpr uint32_t BEXT = 0x74786562;
|
|
constexpr uint32_t JUNK = 0x4B4E554A;
|
|
while (subchunkId == FACT || subchunkId == LIST || subchunkId == BEXT || subchunkId == JUNK)
|
|
{
|
|
SDL_RWseek(rw, subchunkSize, RW_SEEK_CUR);
|
|
subchunkId = SDL_ReadLE32(rw);
|
|
subchunkSize = SDL_ReadLE32(rw);
|
|
if (subchunkId == wantedId)
|
|
{
|
|
return subchunkSize;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Unload()
|
|
{
|
|
if (_rw != nullptr)
|
|
{
|
|
SDL_RWclose(_rw);
|
|
_rw = nullptr;
|
|
}
|
|
_dataBegin = 0;
|
|
_dataLength = 0;
|
|
}
|
|
};
|
|
|
|
IAudioSource* AudioSource::CreateStreamFromWAV(const std::string& path)
|
|
{
|
|
IAudioSource* source = nullptr;
|
|
SDL_RWops* rw = SDL_RWFromFile(path.c_str(), "rb");
|
|
if (rw != nullptr)
|
|
{
|
|
return AudioSource::CreateStreamFromWAV(rw);
|
|
}
|
|
return source;
|
|
}
|
|
|
|
IAudioSource* AudioSource::CreateStreamFromWAV(SDL_RWops* rw)
|
|
{
|
|
auto source = new FileAudioSource();
|
|
if (!source->LoadWAV(rw))
|
|
{
|
|
delete source;
|
|
source = nullptr;
|
|
}
|
|
return source;
|
|
}
|
|
|
|
IAudioSource* AudioSource::CreateStreamFromWAV(std::unique_ptr<IStream> stream)
|
|
{
|
|
auto rw = new SDL_RWops();
|
|
*rw = {};
|
|
rw->type = SDL_RWOPS_UNKNOWN;
|
|
rw->hidden.unknown.data1 = stream.release();
|
|
rw->seek = [](SDL_RWops* ctx, Sint64 offset, int whence) {
|
|
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
|
|
ptr->Seek(offset, whence);
|
|
return static_cast<Sint64>(ptr->GetPosition());
|
|
};
|
|
rw->read = [](SDL_RWops* ctx, void* buf, size_t size, size_t maxnum) {
|
|
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
|
|
return static_cast<size_t>(ptr->TryRead(buf, size * maxnum) / size);
|
|
};
|
|
rw->size = [](SDL_RWops* ctx) {
|
|
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
|
|
return static_cast<Sint64>(ptr->GetLength());
|
|
};
|
|
rw->close = [](SDL_RWops* ctx) {
|
|
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
|
|
delete ptr;
|
|
ctx->hidden.unknown.data1 = nullptr;
|
|
delete ctx;
|
|
return 0;
|
|
};
|
|
return CreateStreamFromWAV(rw);
|
|
}
|
|
|
|
} // namespace OpenRCT2::Audio
|