From c061c8c802cbbc356925f45cc0ab00c86e4fb6f1 Mon Sep 17 00:00:00 2001 From: Nomango Date: Sun, 24 Sep 2023 22:17:04 +0800 Subject: [PATCH] pref: add sound transcoder cache --- src/kiwano-audio/AudioModule.cpp | 49 +++++++-- src/kiwano-audio/AudioModule.h | 12 ++- src/kiwano-audio/Sound.cpp | 166 ++++++++++++++++++------------- src/kiwano-audio/Sound.h | 38 +++---- src/kiwano-audio/Transcoder.h | 59 ++++++++++- 5 files changed, 219 insertions(+), 105 deletions(-) diff --git a/src/kiwano-audio/AudioModule.cpp b/src/kiwano-audio/AudioModule.cpp index 4717c6b0..12b80712 100644 --- a/src/kiwano-audio/AudioModule.cpp +++ b/src/kiwano-audio/AudioModule.cpp @@ -22,11 +22,13 @@ #include #include #include +#include namespace kiwano { namespace audio { + AudioModule::AudioModule() : x_audio2_(nullptr) , mastering_voice_(nullptr) @@ -58,6 +60,8 @@ void AudioModule::DestroyModule() { KGE_DEBUG_LOGF("Destroying audio resources"); + TranscoderCache::GetInstance().Clear(); + if (mastering_voice_) { mastering_voice_->DestroyVoice(); @@ -73,12 +77,45 @@ void AudioModule::DestroyModule() dlls::MediaFoundation::Get().MFShutdown(); } -bool AudioModule::CreateSound(Sound& sound, const Transcoder::Buffer& buffer) +TranscoderPtr AudioModule::CreateTranscoder(const String& file_path) +{ + if (!FileSystem::GetInstance().IsFileExists(file_path)) + { + KGE_WARNF("Media file '%s' not found", file_path.c_str()); + return nullptr; + } + + String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path); + + auto ptr = MakePtr(); + HRESULT hr = ptr->LoadMediaFile(full_path); + if (FAILED(hr)) + { + KGE_ERRORF("Load media file failed with HRESULT of %08X", hr); + return nullptr; + } + return ptr; +} + +TranscoderPtr AudioModule::CreateTranscoder(const Resource& res) +{ + auto ptr = MakePtr(); + HRESULT hr = ptr->LoadMediaResource(res); + if (FAILED(hr)) + { + KGE_ERRORF("Load media resource failed with HRESULT of %08X", hr); + return nullptr; + } + return ptr; +} + +bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder) { KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!"); HRESULT hr = S_OK; + auto buffer = transcoder->GetBuffer(); if (buffer.format == nullptr) hr = E_INVALIDARG; @@ -90,14 +127,8 @@ bool AudioModule::CreateSound(Sound& sound, const Transcoder::Buffer& buffer) if (SUCCEEDED(hr)) { - IXAudio2SourceVoice* old = sound.GetXAudio2Voice(); - if (old) - { - old->DestroyVoice(); - old = nullptr; - } - - sound.SetXAudio2Voice(voice); + sound.Close(); + sound.SetNative(voice); } } diff --git a/src/kiwano-audio/AudioModule.h b/src/kiwano-audio/AudioModule.h index 0e51eb72..05045137 100644 --- a/src/kiwano-audio/AudioModule.h +++ b/src/kiwano-audio/AudioModule.h @@ -60,8 +60,16 @@ public: void Close(); /// \~chinese - /// @brief 从解码器数据缓冲中创建音频对象 - bool CreateSound(Sound& sound, const Transcoder::Buffer& buffer); + /// @brief 创建音频解码器 + TranscoderPtr CreateTranscoder(const String& file_path); + + /// \~chinese + /// @brief 创建音频解码器 + TranscoderPtr CreateTranscoder(const Resource& res); + + /// \~chinese + /// @brief 创建音频 + bool CreateSound(Sound& sound, TranscoderPtr transcoder); public: void SetupModule() override; diff --git a/src/kiwano-audio/Sound.cpp b/src/kiwano-audio/Sound.cpp index e340c9f3..1ba3b031 100644 --- a/src/kiwano-audio/Sound.cpp +++ b/src/kiwano-audio/Sound.cpp @@ -21,12 +21,50 @@ #include #include #include -#include namespace kiwano { namespace audio { + +SoundPtr Sound::Preload(const String& file_path) +{ + auto ptr = MakePtr(); + + size_t hash_code = std::hash{}(file_path); + if (TranscoderPtr transcoder = TranscoderCache::GetInstance().Get(hash_code)) + { + if (ptr->Load(transcoder)) + return ptr; + return nullptr; + } + + if (ptr && ptr->Load(file_path)) + { + TranscoderCache::GetInstance().Add(hash_code, ptr->coder_); + } + return ptr; +} + +SoundPtr Sound::Preload(const Resource& res) +{ + auto ptr = MakePtr(); + + size_t hash_code = res.GetId(); + if (TranscoderPtr transcoder = TranscoderCache::GetInstance().Get(hash_code)) + { + if (ptr->Load(transcoder)) + return ptr; + return nullptr; + } + + if (ptr && ptr->Load(res)) + { + TranscoderCache::GetInstance().Add(hash_code, ptr->coder_); + } + return ptr; +} + Sound::Sound(const String& file_path) : Sound() { @@ -42,7 +80,6 @@ Sound::Sound(const Resource& res) Sound::Sound() : opened_(false) , playing_(false) - , voice_(nullptr) { } @@ -53,34 +90,17 @@ Sound::~Sound() bool Sound::Load(const String& file_path) { - if (!FileSystem::GetInstance().IsFileExists(file_path)) - { - KGE_WARNF("Media file '%s' not found", file_path.c_str()); - return false; - } - if (opened_) { Close(); } - String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path); - - HRESULT hr = transcoder_.LoadMediaFile(full_path); - if (FAILED(hr)) + TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(file_path); + if (!transcoder) { - KGE_ERRORF("Load media file failed with HRESULT of %08X", hr); return false; } - - if (!AudioModule::GetInstance().CreateSound(*this, transcoder_.GetBuffer())) - { - Close(); - return false; - } - - opened_ = true; - return true; + return Load(transcoder); } bool Sound::Load(const Resource& res) @@ -90,26 +110,28 @@ bool Sound::Load(const Resource& res) Close(); } - HRESULT hr = transcoder_.LoadMediaResource(res); - if (FAILED(hr)) + TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(res); + if (!transcoder) { - KGE_ERRORF("Load media resource failed with HRESULT of %08X", hr); return false; } - - if (!AudioModule::GetInstance().CreateSound(*this, transcoder_.GetBuffer())) - { - Close(); - return false; - } - - opened_ = true; - return true; + return Load(transcoder); } -bool Sound::IsValid() const +bool Sound::Load(TranscoderPtr transcoder) { - return voice_ != nullptr; + if (opened_) + { + Close(); + } + if (!AudioModule::GetInstance().CreateSound(*this, transcoder)) + { + return false; + } + + coder_ = transcoder; + opened_ = true; + return true; } void Sound::Play(int loop_count) @@ -120,29 +142,30 @@ void Sound::Play(int loop_count) return; } - KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); + auto voice = GetNativePtr(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); // if sound stream is not empty, stop() will clear it XAUDIO2_VOICE_STATE state; - voice_->GetState(&state); + voice->GetState(&state); if (state.BuffersQueued) Stop(); // clamp loop count loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1); - auto wave_buffer = transcoder_.GetBuffer(); + auto buffer = coder_->GetBuffer(); - XAUDIO2_BUFFER buffer = { 0 }; - buffer.pAudioData = wave_buffer.data; - buffer.Flags = XAUDIO2_END_OF_STREAM; - buffer.AudioBytes = wave_buffer.size; - buffer.LoopCount = static_cast(loop_count); + XAUDIO2_BUFFER xaudio2_buffer = { 0 }; + xaudio2_buffer.pAudioData = buffer.data; + xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM; + xaudio2_buffer.AudioBytes = buffer.size; + xaudio2_buffer.LoopCount = static_cast(loop_count); - HRESULT hr = voice_->SubmitSourceBuffer(&buffer); + HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer); if (SUCCEEDED(hr)) { - hr = voice_->Start(); + hr = voice->Start(); } if (FAILED(hr)) @@ -155,31 +178,34 @@ void Sound::Play(int loop_count) void Sound::Pause() { - KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); + auto voice = GetNativePtr(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); - if (SUCCEEDED(voice_->Stop())) + if (SUCCEEDED(voice->Stop())) playing_ = false; } void Sound::Resume() { - KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); + auto voice = GetNativePtr(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); - if (SUCCEEDED(voice_->Start())) + if (SUCCEEDED(voice->Start())) playing_ = true; } void Sound::Stop() { - KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); + auto voice = GetNativePtr(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); - HRESULT hr = voice_->Stop(); + HRESULT hr = voice->Stop(); if (SUCCEEDED(hr)) - hr = voice_->ExitLoop(); + hr = voice->ExitLoop(); if (SUCCEEDED(hr)) - hr = voice_->FlushSourceBuffers(); + hr = voice->FlushSourceBuffers(); if (SUCCEEDED(hr)) playing_ = false; @@ -187,16 +213,15 @@ void Sound::Stop() void Sound::Close() { - if (voice_) + auto voice = GetNativePtr(); + if (voice) { - voice_->Stop(); - voice_->FlushSourceBuffers(); - voice_->DestroyVoice(); - voice_ = nullptr; + voice->Stop(); + voice->FlushSourceBuffers(); + voice->DestroyVoice(); } - transcoder_.ClearBuffer(); - + coder_ = nullptr; opened_ = false; playing_ = false; } @@ -205,14 +230,15 @@ bool Sound::IsPlaying() const { if (opened_) { - if (!voice_) - return false; - if (!playing_) return false; + auto voice = GetNativePtr(); + if (!voice) + return false; + XAUDIO2_VOICE_STATE state; - voice_->GetState(&state); + voice->GetState(&state); return !!state.BuffersQueued; } return false; @@ -220,19 +246,21 @@ bool Sound::IsPlaying() const float Sound::GetVolume() const { - KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); + auto voice = GetNativePtr(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); float volume = 0.0f; - voice_->GetVolume(&volume); + voice->GetVolume(&volume); return volume; } void Sound::SetVolume(float volume) { - KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); + auto voice = GetNativePtr(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); volume = std::min(std::max(volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL); - voice_->SetVolume(volume); + voice->SetVolume(volume); } } // namespace audio } // namespace kiwano diff --git a/src/kiwano-audio/Sound.h b/src/kiwano-audio/Sound.h index 657cfa0c..2ed34ca6 100644 --- a/src/kiwano-audio/Sound.h +++ b/src/kiwano-audio/Sound.h @@ -21,7 +21,7 @@ #pragma once #include #include -#include +#include #include namespace kiwano @@ -41,11 +41,19 @@ KGE_DECLARE_SMART_PTR(Sound); * \~chinese * @brief 音频对象 */ -class KGE_API Sound : public ObjectBase +class KGE_API Sound : public NativeObject { friend class AudioModule; public: + /// \~chinese + /// @brief 预加载音频 + static SoundPtr Preload(const String& file_path); + + /// \~chinese + /// @brief 预加载音频资源 + static SoundPtr Preload(const Resource& res); + /// \~chinese /// @brief 创建音频对象 /// @param res 本地音频文件路径 @@ -71,8 +79,9 @@ public: bool Load(const Resource& res); /// \~chinese - /// @brief 是否有效 - bool IsValid() const; + /// @brief 打开音频资源 + /// @param res 音频资源 + bool Load(TranscoderPtr transcoder); /// \~chinese /// @brief 播放 @@ -109,27 +118,12 @@ public: void SetVolume(float volume); private: - IXAudio2SourceVoice* GetXAudio2Voice() const; - - void SetXAudio2Voice(IXAudio2SourceVoice* voice); - -private: - bool opened_; - bool playing_; - Transcoder transcoder_; - IXAudio2SourceVoice* voice_; + bool opened_; + bool playing_; + TranscoderPtr coder_; }; /** @} */ -inline IXAudio2SourceVoice* Sound::GetXAudio2Voice() const -{ - return voice_; -} - -inline void Sound::SetXAudio2Voice(IXAudio2SourceVoice* voice) -{ - voice_ = voice; -} } // namespace audio } // namespace kiwano diff --git a/src/kiwano-audio/Transcoder.h b/src/kiwano-audio/Transcoder.h index bd49b3ba..6bc6bc2c 100644 --- a/src/kiwano-audio/Transcoder.h +++ b/src/kiwano-audio/Transcoder.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include #include @@ -28,7 +29,9 @@ namespace kiwano { namespace audio { -class Sound; +class AudioModule; + +KGE_DECLARE_SMART_PTR(Transcoder); /** * \addtogroup Audio @@ -39,9 +42,9 @@ class Sound; * \~chinese * @brief 音频解码器 */ -class KGE_API Transcoder +class KGE_API Transcoder : public ObjectBase { - friend class Sound; + friend class AudioModule; public: /** @@ -86,6 +89,56 @@ private: WAVEFORMATEX* wave_format_; }; + +class KGE_API TranscoderCache final : public Singleton +{ + friend Singleton; + +public: + /// \~chinese + /// @brief 添加缓存 + inline void Add(size_t key, TranscoderPtr v) + { + cache_.insert(std::make_pair(key, v)); + } + + /// \~chinese + /// @brief 获取缓存 + inline TranscoderPtr Get(size_t key) const + { + if (cache_.count(key)) + { + return cache_.at(key); + } + return nullptr; + } + + /// \~chinese + /// @brief 移除缓存 + inline void Remove(size_t key) + { + cache_.erase(key); + } + + /// \~chinese + /// @brief 清空缓存 + inline void Clear() + { + cache_.clear(); + } + + ~TranscoderCache() + { + Clear(); + } + +private: + TranscoderCache() = default; + +private: + UnorderedMap cache_; +}; + /** @} */ } // namespace audio } // namespace kiwano