feat(音频): 添加从NPK加载音频文件的功能
实现SoundPackArchive系统用于管理NPK格式的音频资源包 为Music和Sound类添加loadFromNpk方法支持从NPK加载音频 新增音频播放控制功能如暂停、恢复、停止等
This commit is contained in:
@@ -17,6 +17,7 @@ class Music : public RefObject {
|
|||||||
public:
|
public:
|
||||||
static Ptr<Music> loadFromFile(const std::string& path);
|
static Ptr<Music> loadFromFile(const std::string& path);
|
||||||
static Ptr<Music> loadFromMemory(const uint8* data, size_t size);
|
static Ptr<Music> loadFromMemory(const uint8* data, size_t size);
|
||||||
|
static Ptr<Music> loadFromNpk(const std::string& audioPath);
|
||||||
|
|
||||||
~Music();
|
~Music();
|
||||||
|
|
||||||
@@ -37,9 +38,11 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Music(Mix_Music* music, const std::string& path = "");
|
Music(Mix_Music* music, const std::string& path = "");
|
||||||
|
Music(Mix_Music* music, std::vector<uint8> data, const std::string& path = "");
|
||||||
|
|
||||||
Mix_Music* music_ = nullptr;
|
Mix_Music* music_ = nullptr;
|
||||||
std::string path_;
|
std::string path_;
|
||||||
|
std::vector<uint8> data_;
|
||||||
float volume_ = 1.0f;
|
float volume_ = 1.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,23 @@ class Sound : public RefObject {
|
|||||||
public:
|
public:
|
||||||
static Ptr<Sound> loadFromFile(const std::string& path);
|
static Ptr<Sound> loadFromFile(const std::string& path);
|
||||||
static Ptr<Sound> loadFromMemory(const uint8* data, size_t size);
|
static Ptr<Sound> loadFromMemory(const uint8* data, size_t size);
|
||||||
|
static Ptr<Sound> loadFromNpk(const std::string& audioPath);
|
||||||
|
|
||||||
~Sound();
|
~Sound();
|
||||||
|
|
||||||
int play(int loops = 0, int channel = -1);
|
int play(int loops = 0, int channel = -1);
|
||||||
|
int playLoop(int channel = -1);
|
||||||
void setVolume(float volume);
|
void setVolume(float volume);
|
||||||
float getVolume() const;
|
float getVolume() const;
|
||||||
|
int getLastChannel() const;
|
||||||
|
|
||||||
|
static void stop(int channel);
|
||||||
|
static void stopAll();
|
||||||
|
static bool isPlaying(int channel);
|
||||||
|
static void pause(int channel);
|
||||||
|
static void resume(int channel);
|
||||||
|
static void pauseAll();
|
||||||
|
static void resumeAll();
|
||||||
|
|
||||||
const std::string& getPath() const { return path_; }
|
const std::string& getPath() const { return path_; }
|
||||||
|
|
||||||
@@ -32,6 +43,7 @@ private:
|
|||||||
Mix_Chunk* chunk_ = nullptr;
|
Mix_Chunk* chunk_ = nullptr;
|
||||||
std::string path_;
|
std::string path_;
|
||||||
float volume_ = 1.0f;
|
float volume_ = 1.0f;
|
||||||
|
int lastChannel_ = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <frostbite2D/types/type_alias.h>
|
||||||
|
#include <frostbite2D/resource/binary_reader.h>
|
||||||
|
#include <frostbite2D/resource/asset.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
struct AudioRef {
|
||||||
|
std::string path;
|
||||||
|
std::string npkFile;
|
||||||
|
uint32 offset = 0;
|
||||||
|
uint32 size = 0;
|
||||||
|
bool loaded = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CachedAudioData {
|
||||||
|
std::vector<uint8> data;
|
||||||
|
uint64 lastUseTime = 0;
|
||||||
|
size_t memoryUsage = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SoundPackArchive {
|
||||||
|
public:
|
||||||
|
static SoundPackArchive& get();
|
||||||
|
|
||||||
|
SoundPackArchive(const SoundPackArchive&) = delete;
|
||||||
|
SoundPackArchive& operator=(const SoundPackArchive&) = delete;
|
||||||
|
SoundPackArchive(SoundPackArchive&&) = delete;
|
||||||
|
SoundPackArchive& operator=(SoundPackArchive&&) = delete;
|
||||||
|
|
||||||
|
void setSoundPackDirectory(const std::string& dir);
|
||||||
|
const std::string& getSoundPackDirectory() const;
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void close();
|
||||||
|
bool isOpen() const;
|
||||||
|
|
||||||
|
bool hasAudio(const std::string& path) const;
|
||||||
|
std::optional<AudioRef> getAudio(const std::string& path);
|
||||||
|
std::vector<std::string> listAudios() const;
|
||||||
|
|
||||||
|
std::optional<std::vector<uint8>> getAudioData(const AudioRef& audio);
|
||||||
|
|
||||||
|
void setCacheSize(size_t maxBytes);
|
||||||
|
void clearCache();
|
||||||
|
size_t getCacheUsage() const;
|
||||||
|
|
||||||
|
void setDefaultAudio(const std::string& audioPath);
|
||||||
|
const std::string& getDefaultAudioPath() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SoundPackArchive() = default;
|
||||||
|
~SoundPackArchive() = default;
|
||||||
|
|
||||||
|
std::string normalizePath(const std::string& path) const;
|
||||||
|
void scanNpkFiles();
|
||||||
|
bool parseNpkFile(const std::string& npkPath);
|
||||||
|
bool loadAudioData(AudioRef& audio);
|
||||||
|
void evictCacheIfNeeded(size_t requiredSize);
|
||||||
|
void updateCacheUsage(const std::string& audioPath);
|
||||||
|
std::string readNpkInfoString(BinaryReader& reader);
|
||||||
|
|
||||||
|
static const uint8 NPK_KEY[256];
|
||||||
|
|
||||||
|
std::string soundPackDirectory_ = "SoundPacks";
|
||||||
|
bool initialized_ = false;
|
||||||
|
std::map<std::string, AudioRef> audioIndex_;
|
||||||
|
std::map<std::string, CachedAudioData> audioCache_;
|
||||||
|
std::list<std::string> lruList_;
|
||||||
|
size_t maxCacheSize_ = 256 * 1024 * 1024;
|
||||||
|
size_t currentCacheSize_ = 0;
|
||||||
|
std::string defaultAudioPath_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <frostbite2D/audio/music.h>
|
#include <frostbite2D/audio/music.h>
|
||||||
#include <frostbite2D/audio/audio_system.h>
|
#include <frostbite2D/audio/audio_system.h>
|
||||||
#include <frostbite2D/resource/asset.h>
|
#include <frostbite2D/resource/asset.h>
|
||||||
|
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_mixer.h>
|
#include <SDL2/SDL_mixer.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -21,7 +22,9 @@ Ptr<Music> Music::loadFromFile(const std::string& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Music> Music::loadFromMemory(const uint8* data, size_t size) {
|
Ptr<Music> Music::loadFromMemory(const uint8* data, size_t size) {
|
||||||
SDL_RWops* rw = SDL_RWFromConstMem(data, static_cast<int>(size));
|
std::vector<uint8> dataCopy(data, data + size);
|
||||||
|
|
||||||
|
SDL_RWops* rw = SDL_RWFromConstMem(dataCopy.data(), static_cast<int>(dataCopy.size()));
|
||||||
if (!rw) {
|
if (!rw) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops from memory: %s", SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops from memory: %s", SDL_GetError());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -33,13 +36,46 @@ Ptr<Music> Music::loadFromMemory(const uint8* data, size_t size) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ptr<Music>(new Music(music));
|
return Ptr<Music>(new Music(music, std::move(dataCopy)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<Music> Music::loadFromNpk(const std::string& audioPath) {
|
||||||
|
SoundPackArchive& archive = SoundPackArchive::get();
|
||||||
|
|
||||||
|
if (!archive.isOpen()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SoundPackArchive not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto audioOpt = archive.getAudio(audioPath);
|
||||||
|
if (!audioOpt) {
|
||||||
|
std::string defaultPath = archive.getDefaultAudioPath();
|
||||||
|
if (!defaultPath.empty()) {
|
||||||
|
audioOpt = archive.getAudio(defaultPath);
|
||||||
|
}
|
||||||
|
if (!audioOpt) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio not found in NPK: %s", audioPath.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dataOpt = archive.getAudioData(*audioOpt);
|
||||||
|
if (!dataOpt || dataOpt->empty()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get audio data for: %s", audioPath.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadFromMemory(dataOpt->data(), dataOpt->size());
|
||||||
}
|
}
|
||||||
|
|
||||||
Music::Music(Mix_Music* music, const std::string& path)
|
Music::Music(Mix_Music* music, const std::string& path)
|
||||||
: music_(music), path_(path) {
|
: music_(music), path_(path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Music::Music(Mix_Music* music, std::vector<uint8> data, const std::string& path)
|
||||||
|
: music_(music), path_(path), data_(std::move(data)) {
|
||||||
|
}
|
||||||
|
|
||||||
Music::~Music() {
|
Music::~Music() {
|
||||||
if (music_) {
|
if (music_) {
|
||||||
Mix_FreeMusic(music_);
|
Mix_FreeMusic(music_);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <frostbite2D/audio/sound.h>
|
#include <frostbite2D/audio/sound.h>
|
||||||
#include <frostbite2D/audio/audio_system.h>
|
#include <frostbite2D/audio/audio_system.h>
|
||||||
#include <frostbite2D/resource/asset.h>
|
#include <frostbite2D/resource/asset.h>
|
||||||
|
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_mixer.h>
|
#include <SDL2/SDL_mixer.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -36,6 +37,35 @@ Ptr<Sound> Sound::loadFromMemory(const uint8* data, size_t size) {
|
|||||||
return Ptr<Sound>(new Sound(chunk));
|
return Ptr<Sound>(new Sound(chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ptr<Sound> Sound::loadFromNpk(const std::string& audioPath) {
|
||||||
|
SoundPackArchive& archive = SoundPackArchive::get();
|
||||||
|
|
||||||
|
if (!archive.isOpen()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SoundPackArchive not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto audioOpt = archive.getAudio(audioPath);
|
||||||
|
if (!audioOpt) {
|
||||||
|
std::string defaultPath = archive.getDefaultAudioPath();
|
||||||
|
if (!defaultPath.empty()) {
|
||||||
|
audioOpt = archive.getAudio(defaultPath);
|
||||||
|
}
|
||||||
|
if (!audioOpt) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio not found in NPK: %s", audioPath.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dataOpt = archive.getAudioData(*audioOpt);
|
||||||
|
if (!dataOpt || dataOpt->empty()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get audio data for: %s", audioPath.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadFromMemory(dataOpt->data(), dataOpt->size());
|
||||||
|
}
|
||||||
|
|
||||||
Sound::Sound(Mix_Chunk* chunk, const std::string& path)
|
Sound::Sound(Mix_Chunk* chunk, const std::string& path)
|
||||||
: chunk_(chunk), path_(path) {
|
: chunk_(chunk), path_(path) {
|
||||||
}
|
}
|
||||||
@@ -64,6 +94,8 @@ int Sound::play(int loops, int channel) {
|
|||||||
int result = Mix_PlayChannel(channel, chunk_, loops);
|
int result = Mix_PlayChannel(channel, chunk_, loops);
|
||||||
if (result == -1) {
|
if (result == -1) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to play sound: %s", Mix_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to play sound: %s", Mix_GetError());
|
||||||
|
} else {
|
||||||
|
lastChannel_ = result;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -76,4 +108,49 @@ float Sound::getVolume() const {
|
|||||||
return volume_;
|
return volume_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Sound::getLastChannel() const {
|
||||||
|
return lastChannel_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Sound::playLoop(int channel) {
|
||||||
|
return play(-1, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::stop(int channel) {
|
||||||
|
if (channel >= 0) {
|
||||||
|
Mix_HaltChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::stopAll() {
|
||||||
|
Mix_HaltChannel(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sound::isPlaying(int channel) {
|
||||||
|
if (channel < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Mix_Playing(channel) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::pause(int channel) {
|
||||||
|
if (channel >= 0) {
|
||||||
|
Mix_Pause(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::resume(int channel) {
|
||||||
|
if (channel >= 0) {
|
||||||
|
Mix_Resume(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::pauseAll() {
|
||||||
|
Mix_Pause(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::resumeAll() {
|
||||||
|
Mix_Resume(-1);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
259
Frostbite2D/src/frostbite2D/resource/sound_pack_archive.cpp
Normal file
259
Frostbite2D/src/frostbite2D/resource/sound_pack_archive.cpp
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
const uint8 SoundPackArchive::NPK_KEY[256] = {
|
||||||
|
112, 117, 99, 104, 105, 107, 111, 110, 64, 110, 101, 111, 112, 108, 101, 32,
|
||||||
|
100, 117, 110, 103, 101, 111, 110, 32, 97, 110, 100, 32, 102, 105, 103, 104,
|
||||||
|
116, 101, 114, 32, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78,
|
||||||
|
70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70,
|
||||||
|
68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68,
|
||||||
|
78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78,
|
||||||
|
70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70,
|
||||||
|
68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68,
|
||||||
|
78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
SoundPackArchive& SoundPackArchive::get() {
|
||||||
|
static SoundPackArchive instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::setSoundPackDirectory(const std::string& dir) {
|
||||||
|
soundPackDirectory_ = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& SoundPackArchive::getSoundPackDirectory() const {
|
||||||
|
return soundPackDirectory_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::init() {
|
||||||
|
if (initialized_) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
scanNpkFiles();
|
||||||
|
initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::close() {
|
||||||
|
audioIndex_.clear();
|
||||||
|
audioCache_.clear();
|
||||||
|
lruList_.clear();
|
||||||
|
currentCacheSize_ = 0;
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SoundPackArchive::isOpen() const {
|
||||||
|
return initialized_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SoundPackArchive::normalizePath(const std::string& path) const {
|
||||||
|
std::string result = path;
|
||||||
|
std::transform(result.begin(), result.end(), result.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::scanNpkFiles() {
|
||||||
|
Asset& asset = Asset::get();
|
||||||
|
std::string npkDir = asset.resolvePath(soundPackDirectory_);
|
||||||
|
if (!asset.isDirectory(npkDir)) {
|
||||||
|
SDL_Log("Sound NPK directory not found: %s", npkDir.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> files = asset.listFilesWithExtension(npkDir, ".npk");
|
||||||
|
SDL_Log("Scanning %d sound NPK files...", static_cast<int>(files.size()));
|
||||||
|
for (const auto &file : files) {
|
||||||
|
parseNpkFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SoundPackArchive::parseNpkFile(const std::string& npkPath) {
|
||||||
|
Asset& asset = Asset::get();
|
||||||
|
BinaryReader reader(npkPath);
|
||||||
|
|
||||||
|
if (!reader.isOpen()) {
|
||||||
|
SDL_Log("Failed to open sound NPK file: %s", npkPath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string npkFileName = asset.getFileName(npkPath);
|
||||||
|
std::string header = reader.readNullTerminatedString();
|
||||||
|
|
||||||
|
if (header.find("NeoplePack_Bill") == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 audioCount = reader.readInt32();
|
||||||
|
|
||||||
|
for (int32 i = 0; i < audioCount; ++i) {
|
||||||
|
int32 offset = reader.readInt32();
|
||||||
|
int32 length = reader.readInt32();
|
||||||
|
std::string audioPath = readNpkInfoString(reader);
|
||||||
|
|
||||||
|
AudioRef audio;
|
||||||
|
audio.path = normalizePath(audioPath);
|
||||||
|
audio.npkFile = npkFileName;
|
||||||
|
audio.offset = static_cast<uint32>(offset);
|
||||||
|
audio.size = static_cast<uint32>(length);
|
||||||
|
audio.loaded = false;
|
||||||
|
|
||||||
|
audioIndex_[audio.path] = audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SoundPackArchive::hasAudio(const std::string& path) const {
|
||||||
|
return audioIndex_.find(normalizePath(path)) != audioIndex_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AudioRef> SoundPackArchive::getAudio(const std::string& path) {
|
||||||
|
auto it = audioIndex_.find(normalizePath(path));
|
||||||
|
if (it == audioIndex_.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SoundPackArchive::listAudios() const {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
result.reserve(audioIndex_.size());
|
||||||
|
for (const auto& pair : audioIndex_) {
|
||||||
|
result.push_back(pair.first);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::vector<uint8>> SoundPackArchive::getAudioData(const AudioRef& audio) {
|
||||||
|
std::string audioPath = normalizePath(audio.path);
|
||||||
|
|
||||||
|
auto cacheIt = audioCache_.find(audioPath);
|
||||||
|
if (cacheIt == audioCache_.end()) {
|
||||||
|
auto it = audioIndex_.find(audioPath);
|
||||||
|
if (it == audioIndex_.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loadAudioData(it->second)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
cacheIt = audioCache_.find(audioPath);
|
||||||
|
if (cacheIt == audioCache_.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCacheUsage(audioPath);
|
||||||
|
return cacheIt->second.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SoundPackArchive::loadAudioData(AudioRef& audio) {
|
||||||
|
Asset& asset = Asset::get();
|
||||||
|
std::string npkPath = asset.combinePath(soundPackDirectory_, audio.npkFile);
|
||||||
|
npkPath = asset.resolvePath(npkPath);
|
||||||
|
|
||||||
|
BinaryReader reader(npkPath);
|
||||||
|
if (!reader.isOpen()) {
|
||||||
|
SDL_Log("Failed to open NPK for audio: %s", npkPath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.seek(audio.offset);
|
||||||
|
std::vector<uint8> audioData = reader.readBytes(audio.size);
|
||||||
|
|
||||||
|
if (audioData.size() != audio.size) {
|
||||||
|
SDL_Log("Failed to read complete audio data");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
evictCacheIfNeeded(audioData.size());
|
||||||
|
|
||||||
|
CachedAudioData cachedData;
|
||||||
|
cachedData.data = std::move(audioData);
|
||||||
|
cachedData.memoryUsage = cachedData.data.size();
|
||||||
|
cachedData.lastUseTime = SDL_GetTicks();
|
||||||
|
|
||||||
|
audioCache_[audio.path] = std::move(cachedData);
|
||||||
|
currentCacheSize_ += audioCache_[audio.path].memoryUsage;
|
||||||
|
lruList_.push_front(audio.path);
|
||||||
|
|
||||||
|
audio.loaded = true;
|
||||||
|
audioIndex_[audio.path] = audio;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::setCacheSize(size_t maxBytes) {
|
||||||
|
maxCacheSize_ = maxBytes;
|
||||||
|
evictCacheIfNeeded(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::clearCache() {
|
||||||
|
audioCache_.clear();
|
||||||
|
lruList_.clear();
|
||||||
|
currentCacheSize_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SoundPackArchive::getCacheUsage() const {
|
||||||
|
return currentCacheSize_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::evictCacheIfNeeded(size_t requiredSize) {
|
||||||
|
while (currentCacheSize_ + requiredSize > maxCacheSize_ && !lruList_.empty()) {
|
||||||
|
std::string oldest = lruList_.back();
|
||||||
|
lruList_.pop_back();
|
||||||
|
|
||||||
|
auto it = audioCache_.find(oldest);
|
||||||
|
if (it != audioCache_.end()) {
|
||||||
|
currentCacheSize_ -= it->second.memoryUsage;
|
||||||
|
audioCache_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::updateCacheUsage(const std::string& audioPath) {
|
||||||
|
auto it = std::find(lruList_.begin(), lruList_.end(), audioPath);
|
||||||
|
if (it != lruList_.end()) {
|
||||||
|
lruList_.erase(it);
|
||||||
|
}
|
||||||
|
lruList_.push_front(audioPath);
|
||||||
|
|
||||||
|
auto cacheIt = audioCache_.find(audioPath);
|
||||||
|
if (cacheIt != audioCache_.end()) {
|
||||||
|
cacheIt->second.lastUseTime = SDL_GetTicks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SoundPackArchive::readNpkInfoString(BinaryReader& reader) {
|
||||||
|
if (reader.eof()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8> encrypted = reader.readBytes(256);
|
||||||
|
if (encrypted.size() < 256) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> decrypted(256);
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
decrypted[i] = static_cast<char>(encrypted[i] ^ NPK_KEY[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(decrypted.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPackArchive::setDefaultAudio(const std::string& audioPath) {
|
||||||
|
defaultAudioPath_ = normalizePath(audioPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& SoundPackArchive::getDefaultAudioPath() const {
|
||||||
|
return defaultAudioPath_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
Game/assets/SoundPacks/sounds_ui.npk
Normal file
BIN
Game/assets/SoundPacks/sounds_ui.npk
Normal file
Binary file not shown.
@@ -18,6 +18,8 @@
|
|||||||
#include <frostbite2D/audio/sound.h>
|
#include <frostbite2D/audio/sound.h>
|
||||||
#include <frostbite2D/resource/npk_archive.h>
|
#include <frostbite2D/resource/npk_archive.h>
|
||||||
|
|
||||||
|
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||||
|
|
||||||
using namespace frostbite2D;
|
using namespace frostbite2D;
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
@@ -117,11 +119,30 @@ int main(int argc, char **argv) {
|
|||||||
auto sprite1 = Sprite::createFromNpk("sprite/newtitle/nangua.img", 0);
|
auto sprite1 = Sprite::createFromNpk("sprite/newtitle/nangua.img", 0);
|
||||||
if (sprite1) {
|
if (sprite1) {
|
||||||
sprite1->SetPosition(0, 0);
|
sprite1->SetPosition(0, 0);
|
||||||
|
sprite1->SetScale(5.0f);
|
||||||
menuScene->AddActor(sprite1);
|
menuScene->AddActor(sprite1);
|
||||||
} else {
|
} else {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from NPK!");
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from NPK!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SoundPackArchive &archive = SoundPackArchive::get();
|
||||||
|
archive.setSoundPackDirectory("assets/SoundPacks");
|
||||||
|
archive.init();
|
||||||
|
|
||||||
|
// auto sound = Sound::loadFromNpk("sounds/ui/adventurer_maker_name.ogg");
|
||||||
|
// if (sound) {
|
||||||
|
// sound->play();
|
||||||
|
// } else {
|
||||||
|
// SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load sound!");
|
||||||
|
// }
|
||||||
|
|
||||||
|
auto music = Music::loadFromNpk("sounds/ui/amazing_box.ogg");
|
||||||
|
if (music) {
|
||||||
|
music->play();
|
||||||
|
} else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load music!");
|
||||||
|
}
|
||||||
|
|
||||||
app.run();
|
app.run();
|
||||||
|
|
||||||
app.shutdown();
|
app.shutdown();
|
||||||
|
|||||||
Reference in New Issue
Block a user