Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/core
diff options
context:
space:
mode:
authorbmdhacks <bmd@bmdhacks.com>2025-02-13 16:01:47 -0800
committerbmdhacks <bmd@bmdhacks.com>2025-02-15 14:37:57 -0800
commit4ece0d79210c0f0351c16a0611f40308ceb8efb0 (patch)
tree7f27a77ccc27cdf0486d45feb6aa5ca484474e84 /source/core
parenteedd20da00e873ae188634f5e9af4a2e9efe8607 (diff)
Streaming Audio
Problem: The current implementation reads the entire sound file in to memory in order to play it. For OGG background music, this uses 91 Megs of RAM by injesting all ogg songs as well as all wav assets. Solution: Change StarAudio to play the asset from a file handle and stream it off the disk. This results in a massive savings of RAM and doesn't really affect audio quality unless you're doing massive disk operations such as compiling Starbound.
Diffstat (limited to 'source/core')
-rw-r--r--source/core/CMakeLists.txt4
-rw-r--r--source/core/StarAudio.cpp191
-rw-r--r--source/core/StarBuffer.cpp13
-rw-r--r--source/core/StarBuffer.hpp4
-rw-r--r--source/core/StarCompression.cpp12
-rw-r--r--source/core/StarCompression.hpp2
-rw-r--r--source/core/StarFile.cpp11
-rw-r--r--source/core/StarFile.hpp2
-rw-r--r--source/core/StarIODevice.hpp3
-rw-r--r--source/core/StarIODeviceCallbacks.cpp39
-rw-r--r--source/core/StarIODeviceCallbacks.hpp37
11 files changed, 238 insertions, 80 deletions
diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt
index 3d00baf..63f86cf 100644
--- a/source/core/CMakeLists.txt
+++ b/source/core/CMakeLists.txt
@@ -10,6 +10,7 @@ SET (star_core_HEADERS
StarAssetPath.hpp
StarAtomicSharedPtr.hpp
StarAudio.hpp
+ StarIODeviceCallbacks.hpp
StarBTree.hpp
StarBTreeDatabase.hpp
StarBiMap.hpp
@@ -141,6 +142,7 @@ SET (star_core_SOURCES
StarBuffer.cpp
StarByteArray.cpp
StarColor.cpp
+ StarIODeviceCallbacks.cpp
StarCompression.cpp
StarCurve25519.cpp
StarDataStream.cpp
@@ -235,4 +237,4 @@ IF(STAR_USE_JEMALLOC AND JEMALLOC_IS_PREFIXED)
SET_SOURCE_FILES_PROPERTIES(StarMemory.cpp PROPERTIES
COMPILE_DEFINITIONS STAR_JEMALLOC_IS_PREFIXED
)
-ENDIF() \ No newline at end of file
+ENDIF()
diff --git a/source/core/StarAudio.cpp b/source/core/StarAudio.cpp
index a1f2e62..1b7e78e 100644
--- a/source/core/StarAudio.cpp
+++ b/source/core/StarAudio.cpp
@@ -6,10 +6,13 @@
#include "StarAudio.hpp"
#include "StarBuffer.hpp"
+#include "StarIODeviceCallbacks.hpp"
#include "StarFile.hpp"
#include "StarFormat.hpp"
#include "StarLogging.hpp"
#include "StarDataStreamDevices.hpp"
+#include "StarSha256.hpp"
+#include "StarEncode.hpp"
namespace Star {
@@ -35,9 +38,10 @@ float amplitudeToPerceptual(float amp, float normalizedMax, float range, float b
namespace {
struct WaveData {
- ByteArrayPtr byteArray;
+ IODevicePtr device;
unsigned channels;
unsigned sampleRate;
+ size_t dataSize; // get the data size from the header to avoid id3 tag
};
template <typename T>
@@ -141,59 +145,59 @@ namespace {
device->size(), wavDataSize + wavDataOffset));
}
- ByteArrayPtr pcmData = make_shared<ByteArray>();
- pcmData->resize(wavDataSize);
-
- // Copy across data and perform and endianess conversion if needed
- device->readFull(pcmData->ptr(), pcmData->size());
- for (size_t i = 0; i < pcmData->size() / 2; ++i)
- fromByteOrder(ByteOrder::LittleEndian, pcmData->ptr() + i * 2, 2);
-
- return WaveData{std::move(pcmData), wavChannels, wavSampleRate};
+ // Return the original device positioned at the PCM data
+ // Note: This means the caller owns handling endianness conversion
+ device->seek(wavDataOffset);
+
+ return WaveData{device, wavChannels, wavSampleRate, wavDataSize};
}
}
class CompressedAudioImpl {
public:
- static size_t readFunc(void* ptr, size_t size, size_t nmemb, void* datasource) {
- return static_cast<ExternalBuffer*>(datasource)->read((char*)ptr, size * nmemb) / size;
- }
- static int seekFunc(void* datasource, ogg_int64_t offset, int whence) {
- static_cast<ExternalBuffer*>(datasource)->seek(offset, (IOSeek)whence);
- return 0;
- };
+ CompressedAudioImpl(CompressedAudioImpl const& impl)
+ : m_audioData(impl.m_audioData->clone()) // Clone instead of sharing
+ , m_deviceCallbacks(m_audioData) // Pass reference to cloned data
+ , m_vorbisInfo(nullptr) {
+ setupCallbacks();
- static long int tellFunc(void* datasource) {
- return (long int)static_cast<ExternalBuffer*>(datasource)->pos();
- };
+ // Make sure data stream is ready to be read
+ m_audioData->open(IOMode::Read);
+ m_audioData->seek(0);
+
+ // Add error checking to see what's happening with the clone
+ if (!m_audioData->isOpen())
+ throw AudioException("Failed to open cloned audio device");
- CompressedAudioImpl(CompressedAudioImpl const& impl) {
- m_audioData = impl.m_audioData;
- m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
- m_vorbisInfo = nullptr;
+ auto size = m_audioData->size();
+ if (size <= 0)
+ throw AudioException("Cloned audio device has no data");
}
- CompressedAudioImpl(IODevicePtr audioData) {
- audioData->open(IOMode::Read);
- audioData->seek(0);
- m_audioData = make_shared<ByteArray>(audioData->readBytes((size_t)audioData->size()));
- m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
- m_vorbisInfo = nullptr;
+ CompressedAudioImpl(IODevicePtr audioData)
+ : m_audioData(audioData->clone()) // Clone instead of taking ownership
+ , m_deviceCallbacks(m_audioData) // Pass reference
+ , m_vorbisInfo(nullptr) {
+ setupCallbacks();
+ m_audioData->open(IOMode::Read);
+ m_audioData->seek(0);
}
~CompressedAudioImpl() {
ov_clear(&m_vorbisFile);
}
- bool open() {
- m_callbacks.read_func = readFunc;
- m_callbacks.seek_func = seekFunc;
- m_callbacks.tell_func = tellFunc;
- m_callbacks.close_func = NULL;
+ void setupCallbacks() {
+ m_deviceCallbacks.setupOggCallbacks(m_callbacks);
+ }
- if (ov_open_callbacks(&m_memoryFile, &m_vorbisFile, NULL, 0, m_callbacks) < 0)
+ bool open() {
+ int result = ov_open_callbacks(&m_deviceCallbacks, &m_vorbisFile, NULL, 0, m_callbacks);
+ if (result < 0) {
+ Logger::error("Failed to open ogg stream: error code {}", result);
return false;
+ }
m_vorbisInfo = ov_info(&m_vorbisFile, -1);
return true;
@@ -252,14 +256,14 @@ public:
} while (read == OV_HOLE);
if (read < 0)
throw AudioException::format("Error in Audio::read ({})", read);
-
+
// read in bytes, returning number of int16_t samples.
return read / 2;
}
-
+
private:
- ByteArrayConstPtr m_audioData;
- ExternalBuffer m_memoryFile;
+ IODevicePtr m_audioData;
+ IODeviceCallbacks m_deviceCallbacks;
ov_callbacks m_callbacks;
OggVorbis_File m_vorbisFile;
vorbis_info* m_vorbisInfo;
@@ -267,41 +271,49 @@ private:
class UncompressedAudioImpl {
public:
- UncompressedAudioImpl(UncompressedAudioImpl const& impl) {
- m_channels = impl.m_channels;
- m_sampleRate = impl.m_sampleRate;
- m_audioData = impl.m_audioData;
- m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
- }
-
+ UncompressedAudioImpl(UncompressedAudioImpl const& impl)
+ : m_device(impl.m_device->clone())
+ , m_channels(impl.m_channels)
+ , m_sampleRate(impl.m_sampleRate)
+ , m_dataSize(impl.m_dataSize)
+ , m_dataStart(impl.m_dataStart)
+
+ {
+ StreamOffset initialPos = m_device->pos(); // Store initial position
+ if (!m_device->isOpen())
+ m_device->open(IOMode::Read);
+ m_device->seek(initialPos); // Restore position after open
+ }
+
UncompressedAudioImpl(CompressedAudioImpl& impl) {
m_channels = impl.channels();
m_sampleRate = impl.sampleRate();
+ // Create a memory buffer to store decompressed data
+ auto memDevice = make_shared<Buffer>();
+
int16_t buffer[1024];
- Buffer uncompressBuffer;
while (true) {
size_t ramt = impl.readPartial(buffer, 1024);
-
- if (ramt == 0) {
- // End of stream reached
+ if (ramt == 0)
break;
- } else {
- uncompressBuffer.writeFull((char*)buffer, ramt * 2);
- }
+ memDevice->writeFull((char*)buffer, ramt * 2);
}
- m_audioData = make_shared<ByteArray>(uncompressBuffer.takeData());
- m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
+ m_device = memDevice;
}
- UncompressedAudioImpl(ByteArrayConstPtr data, unsigned channels, unsigned sampleRate) {
- m_channels = channels;
- m_sampleRate = sampleRate;
- m_audioData = std::move(data);
- m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
+ UncompressedAudioImpl(IODevicePtr device, unsigned channels, unsigned sampleRate, size_t dataSize)
+ : m_device(std::move(device))
+ , m_channels(channels)
+ , m_sampleRate(sampleRate)
+ , m_dataSize(dataSize)
+ , m_dataStart((size_t)m_device->pos()) // Store current position as data start
+ {
+ if (!m_device->isOpen())
+ m_device->open(IOMode::Read);
}
-
+
bool open() {
return true;
}
@@ -319,7 +331,7 @@ public:
}
uint64_t totalSamples() {
- return m_memoryFile.dataSize() / 2 / m_channels;
+ return m_device->size() / 2 / m_channels;
}
void seekTime(double time) {
@@ -327,7 +339,7 @@ public:
}
void seekSample(uint64_t pos) {
- m_memoryFile.seek(pos * 2 * m_channels);
+ m_device->seek(pos * 2 * m_channels);
}
double currentTime() {
@@ -335,20 +347,41 @@ public:
}
uint64_t currentSample() {
- return m_memoryFile.pos() / 2 / m_channels;
+ return m_device->pos() / 2 / m_channels;
}
+
size_t readPartial(int16_t* buffer, size_t bufferSize) {
if (bufferSize != NPos)
bufferSize = bufferSize * 2;
- return m_memoryFile.read((char*)buffer, bufferSize) / 2;
+
+ // Calculate remaining valid data
+ size_t currentPos = m_device->pos() - m_dataStart;
+ size_t remainingBytes = m_dataSize - currentPos;
+
+ // Limit read to remaining valid data
+ if (bufferSize > remainingBytes)
+ bufferSize = remainingBytes;
+
+ if (bufferSize == 0)
+ return 0;
+
+ size_t bytesRead = m_device->read((char*)buffer, bufferSize);
+
+ // Handle endianness conversion
+ for (size_t i = 0; i < bytesRead / 2; ++i)
+ fromByteOrder(ByteOrder::LittleEndian, ((char*)buffer) + i * 2, 2);
+
+ return bytesRead / 2;
+
}
private:
+ IODevicePtr m_device;
unsigned m_channels;
unsigned m_sampleRate;
- ByteArrayConstPtr m_audioData;
- ExternalBuffer m_memoryFile;
+ size_t m_dataSize;
+ size_t m_dataStart;
};
Audio::Audio(IODevicePtr device, String name) {
@@ -358,7 +391,7 @@ Audio::Audio(IODevicePtr device, String name) {
if (isUncompressed(device)) {
WaveData data = parseWav(device);
- m_uncompressed = make_shared<UncompressedAudioImpl>(std::move(data.byteArray), data.channels, data.sampleRate);
+ m_uncompressed = make_shared<UncompressedAudioImpl>(std::move(data.device), data.channels, data.sampleRate, data.dataSize);
} else {
m_compressed = make_shared<CompressedAudioImpl>(device);
if (!m_compressed->open())
@@ -375,16 +408,16 @@ Audio::Audio(Audio&& audio) {
}
Audio& Audio::operator=(Audio const& audio) {
- if (audio.m_uncompressed) {
- m_uncompressed = make_shared<UncompressedAudioImpl>(*audio.m_uncompressed);
- m_uncompressed->open();
- } else {
- m_compressed = make_shared<CompressedAudioImpl>(*audio.m_compressed);
- m_compressed->open();
- }
-
- seekSample(audio.currentSample());
- return *this;
+ if (audio.m_uncompressed) {
+ m_uncompressed = make_shared<UncompressedAudioImpl>(*audio.m_uncompressed);
+ m_uncompressed->open();
+ } else {
+ m_compressed = make_shared<CompressedAudioImpl>(*audio.m_compressed);
+ if (!m_compressed->open()) // Check the return value
+ throw AudioException("Failed to open compressed audio stream during copy");
+ seekSample(audio.currentSample()); // Only seek after successful open
+ }
+ return *this;
}
Audio& Audio::operator=(Audio&& audio) {
diff --git a/source/core/StarBuffer.cpp b/source/core/StarBuffer.cpp
index 5b5b2e4..e8184e3 100644
--- a/source/core/StarBuffer.cpp
+++ b/source/core/StarBuffer.cpp
@@ -2,6 +2,7 @@
#include "StarMathCommon.hpp"
#include "StarIODevice.hpp"
#include "StarFormat.hpp"
+#include "StarLogging.hpp"
namespace Star {
@@ -273,6 +274,18 @@ void ExternalBuffer::reset(char const* externalData, size_t len) {
m_size = len;
}
+IODevicePtr Buffer::clone() {
+ auto cloned = make_shared<Buffer>(*this);
+ // Reset position to 0 while preserving mode and data
+ cloned->seek(0);
+ return cloned;
+}
+
+IODevicePtr ExternalBuffer::clone() {
+ Logger::info("Cloning ExternalBuffer from position {}");
+ return make_shared<ExternalBuffer>(*this);
+}
+
size_t ExternalBuffer::doRead(size_t pos, char* data, size_t len) {
if (len == 0)
return 0;
diff --git a/source/core/StarBuffer.hpp b/source/core/StarBuffer.hpp
index 0f9864a..4870b53 100644
--- a/source/core/StarBuffer.hpp
+++ b/source/core/StarBuffer.hpp
@@ -34,6 +34,8 @@ public:
String deviceName() const override;
StreamOffset size() override;
+
+ IODevicePtr clone() override;
ByteArray& data();
ByteArray const& data() const;
@@ -95,6 +97,8 @@ public:
String deviceName() const override;
StreamOffset size() override;
+
+ IODevicePtr clone() override;
// Returns a pointer to the beginning of the Buffer.
char const* ptr() const;
diff --git a/source/core/StarCompression.cpp b/source/core/StarCompression.cpp
index 58b43ef..d99745c 100644
--- a/source/core/StarCompression.cpp
+++ b/source/core/StarCompression.cpp
@@ -233,4 +233,16 @@ void CompressedFile::close() {
setMode(IOMode::Closed);
}
+IODevicePtr CompressedFile::clone() {
+ auto cloned = make_shared<CompressedFile>(m_filename);
+ cloned->setCompression(m_compression);
+ if (isOpen()) {
+ // Open with same mode
+ cloned->open(mode());
+ // Seek to same position
+ cloned->seek(pos());
+ }
+ return cloned;
+}
+
}
diff --git a/source/core/StarCompression.hpp b/source/core/StarCompression.hpp
index 3322662..5ffe791 100644
--- a/source/core/StarCompression.hpp
+++ b/source/core/StarCompression.hpp
@@ -49,6 +49,8 @@ public:
void sync() override;
void close() override;
+ IODevicePtr clone() override;
+
private:
String m_filename;
void* m_file;
diff --git a/source/core/StarFile.cpp b/source/core/StarFile.cpp
index 9153960..75813b4 100644
--- a/source/core/StarFile.cpp
+++ b/source/core/StarFile.cpp
@@ -243,4 +243,15 @@ String File::deviceName() const {
return m_filename;
}
+IODevicePtr File::clone() {
+ auto cloned = make_shared<File>(m_filename);
+ if (isOpen()) {
+ // Open with same mode
+ cloned->open(mode());
+ // Seek to same position
+ cloned->seek(pos());
+ }
+ return cloned;
+}
+
}
diff --git a/source/core/StarFile.hpp b/source/core/StarFile.hpp
index fb489ab..36c8390 100644
--- a/source/core/StarFile.hpp
+++ b/source/core/StarFile.hpp
@@ -127,6 +127,8 @@ public:
String deviceName() const override;
+ IODevicePtr clone() override;
+
private:
static void* fopen(char const* filename, IOMode mode);
static void fseek(void* file, StreamOffset offset, IOSeek seek);
diff --git a/source/core/StarIODevice.hpp b/source/core/StarIODevice.hpp
index b375e81..7bc2a04 100644
--- a/source/core/StarIODevice.hpp
+++ b/source/core/StarIODevice.hpp
@@ -70,6 +70,9 @@ public:
// Default implementation is a no-op
virtual void sync();
+ // Returns a clone of this device with the same mode
+ virtual IODevicePtr clone() = 0;
+
// Default implementation just prints address of generic IODevice
virtual String deviceName() const;
diff --git a/source/core/StarIODeviceCallbacks.cpp b/source/core/StarIODeviceCallbacks.cpp
new file mode 100644
index 0000000..ca8b692
--- /dev/null
+++ b/source/core/StarIODeviceCallbacks.cpp
@@ -0,0 +1,39 @@
+#include "StarIODeviceCallbacks.hpp"
+#include "vorbis/vorbisfile.h"
+
+namespace Star {
+
+IODeviceCallbacks::IODeviceCallbacks(IODevicePtr device)
+ : m_device(std::move(device)) {
+ if (!m_device->isOpen())
+ m_device->open(IOMode::Read);
+}
+
+IODevicePtr const& IODeviceCallbacks::device() const {
+ return m_device;
+}
+
+size_t IODeviceCallbacks::readFunc(void* ptr, size_t size, size_t nmemb, void* datasource) {
+ auto* callbacks = static_cast<IODeviceCallbacks*>(datasource);
+ return callbacks->m_device->read((char*)ptr, size * nmemb) / size;
+}
+
+int IODeviceCallbacks::seekFunc(void* datasource, ogg_int64_t offset, int whence) {
+ auto* callbacks = static_cast<IODeviceCallbacks*>(datasource);
+ callbacks->m_device->seek(offset, (IOSeek)whence);
+ return 0;
+}
+
+long int IODeviceCallbacks::tellFunc(void* datasource) {
+ auto* callbacks = static_cast<IODeviceCallbacks*>(datasource);
+ return (long int)callbacks->m_device->pos();
+}
+
+void IODeviceCallbacks::setupOggCallbacks(ov_callbacks& callbacks) {
+ callbacks.read_func = readFunc;
+ callbacks.seek_func = seekFunc;
+ callbacks.tell_func = tellFunc;
+ callbacks.close_func = nullptr;
+}
+
+} \ No newline at end of file
diff --git a/source/core/StarIODeviceCallbacks.hpp b/source/core/StarIODeviceCallbacks.hpp
new file mode 100644
index 0000000..8e2f9ed
--- /dev/null
+++ b/source/core/StarIODeviceCallbacks.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "StarIODevice.hpp"
+#include "vorbis/codec.h"
+#include "vorbis/vorbisfile.h"
+
+namespace Star {
+
+// Provides callbacks for interfacing IODevice with ogg vorbis callbacks
+class IODeviceCallbacks {
+public:
+ explicit IODeviceCallbacks(IODevicePtr device);
+
+ // No copying
+ IODeviceCallbacks(IODeviceCallbacks const&) = delete;
+ IODeviceCallbacks& operator=(IODeviceCallbacks const&) = delete;
+
+ // Moving is ok
+ IODeviceCallbacks(IODeviceCallbacks&&) = default;
+ IODeviceCallbacks& operator=(IODeviceCallbacks&&) = default;
+
+ // Get the underlying device
+ IODevicePtr const& device() const;
+
+ // Callback functions for Ogg Vorbis
+ static size_t readFunc(void* ptr, size_t size, size_t nmemb, void* datasource);
+ static int seekFunc(void* datasource, ogg_int64_t offset, int whence);
+ static long int tellFunc(void* datasource);
+
+ // Sets up callbacks for Ogg Vorbis
+ void setupOggCallbacks(ov_callbacks& callbacks);
+
+private:
+ IODevicePtr m_device;
+};
+
+}