pref: add sound transcoder cache

This commit is contained in:
Nomango 2023-09-24 22:17:04 +08:00
parent 03c24091c7
commit c061c8c802
5 changed files with 219 additions and 105 deletions

View File

@ -22,11 +22,13 @@
#include <kiwano-audio/libraries.h> #include <kiwano-audio/libraries.h>
#include <kiwano/core/Exception.h> #include <kiwano/core/Exception.h>
#include <kiwano/utils/Logger.h> #include <kiwano/utils/Logger.h>
#include <kiwano/platform/FileSystem.h>
namespace kiwano namespace kiwano
{ {
namespace audio namespace audio
{ {
AudioModule::AudioModule() AudioModule::AudioModule()
: x_audio2_(nullptr) : x_audio2_(nullptr)
, mastering_voice_(nullptr) , mastering_voice_(nullptr)
@ -58,6 +60,8 @@ void AudioModule::DestroyModule()
{ {
KGE_DEBUG_LOGF("Destroying audio resources"); KGE_DEBUG_LOGF("Destroying audio resources");
TranscoderCache::GetInstance().Clear();
if (mastering_voice_) if (mastering_voice_)
{ {
mastering_voice_->DestroyVoice(); mastering_voice_->DestroyVoice();
@ -73,12 +77,45 @@ void AudioModule::DestroyModule()
dlls::MediaFoundation::Get().MFShutdown(); 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<Transcoder>();
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<Transcoder>();
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!"); KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!");
HRESULT hr = S_OK; HRESULT hr = S_OK;
auto buffer = transcoder->GetBuffer();
if (buffer.format == nullptr) if (buffer.format == nullptr)
hr = E_INVALIDARG; hr = E_INVALIDARG;
@ -90,14 +127,8 @@ bool AudioModule::CreateSound(Sound& sound, const Transcoder::Buffer& buffer)
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
IXAudio2SourceVoice* old = sound.GetXAudio2Voice(); sound.Close();
if (old) sound.SetNative(voice);
{
old->DestroyVoice();
old = nullptr;
}
sound.SetXAudio2Voice(voice);
} }
} }

View File

@ -60,8 +60,16 @@ public:
void Close(); void Close();
/// \~chinese /// \~chinese
/// @brief 从解码器数据缓冲中创建音频对象 /// @brief 创建音频解码器
bool CreateSound(Sound& sound, const Transcoder::Buffer& buffer); TranscoderPtr CreateTranscoder(const String& file_path);
/// \~chinese
/// @brief 创建音频解码器
TranscoderPtr CreateTranscoder(const Resource& res);
/// \~chinese
/// @brief 创建音频
bool CreateSound(Sound& sound, TranscoderPtr transcoder);
public: public:
void SetupModule() override; void SetupModule() override;

View File

@ -21,12 +21,50 @@
#include <kiwano-audio/AudioModule.h> #include <kiwano-audio/AudioModule.h>
#include <kiwano-audio/Sound.h> #include <kiwano-audio/Sound.h>
#include <kiwano/utils/Logger.h> #include <kiwano/utils/Logger.h>
#include <kiwano/platform/FileSystem.h>
namespace kiwano namespace kiwano
{ {
namespace audio namespace audio
{ {
SoundPtr Sound::Preload(const String& file_path)
{
auto ptr = MakePtr<Sound>();
size_t hash_code = std::hash<String>{}(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<Sound>();
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::Sound(const String& file_path)
: Sound() : Sound()
{ {
@ -42,7 +80,6 @@ Sound::Sound(const Resource& res)
Sound::Sound() Sound::Sound()
: opened_(false) : opened_(false)
, playing_(false) , playing_(false)
, voice_(nullptr)
{ {
} }
@ -53,34 +90,17 @@ Sound::~Sound()
bool Sound::Load(const String& file_path) 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_) if (opened_)
{ {
Close(); Close();
} }
String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path); TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(file_path);
if (!transcoder)
HRESULT hr = transcoder_.LoadMediaFile(full_path);
if (FAILED(hr))
{ {
KGE_ERRORF("Load media file failed with HRESULT of %08X", hr);
return false; return false;
} }
return Load(transcoder);
if (!AudioModule::GetInstance().CreateSound(*this, transcoder_.GetBuffer()))
{
Close();
return false;
}
opened_ = true;
return true;
} }
bool Sound::Load(const Resource& res) bool Sound::Load(const Resource& res)
@ -90,26 +110,28 @@ bool Sound::Load(const Resource& res)
Close(); Close();
} }
HRESULT hr = transcoder_.LoadMediaResource(res); TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(res);
if (FAILED(hr)) if (!transcoder)
{ {
KGE_ERRORF("Load media resource failed with HRESULT of %08X", hr);
return false; return false;
} }
return Load(transcoder);
if (!AudioModule::GetInstance().CreateSound(*this, transcoder_.GetBuffer()))
{
Close();
return false;
}
opened_ = true;
return true;
} }
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) void Sound::Play(int loop_count)
@ -120,29 +142,30 @@ void Sound::Play(int loop_count)
return; return;
} }
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
// if sound stream is not empty, stop() will clear it // if sound stream is not empty, stop() will clear it
XAUDIO2_VOICE_STATE state; XAUDIO2_VOICE_STATE state;
voice_->GetState(&state); voice->GetState(&state);
if (state.BuffersQueued) if (state.BuffersQueued)
Stop(); Stop();
// clamp loop count // clamp loop count
loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1); 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 }; XAUDIO2_BUFFER xaudio2_buffer = { 0 };
buffer.pAudioData = wave_buffer.data; xaudio2_buffer.pAudioData = buffer.data;
buffer.Flags = XAUDIO2_END_OF_STREAM; xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM;
buffer.AudioBytes = wave_buffer.size; xaudio2_buffer.AudioBytes = buffer.size;
buffer.LoopCount = static_cast<uint32_t>(loop_count); xaudio2_buffer.LoopCount = static_cast<uint32_t>(loop_count);
HRESULT hr = voice_->SubmitSourceBuffer(&buffer); HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer);
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
hr = voice_->Start(); hr = voice->Start();
} }
if (FAILED(hr)) if (FAILED(hr))
@ -155,31 +178,34 @@ void Sound::Play(int loop_count)
void Sound::Pause() void Sound::Pause()
{ {
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
if (SUCCEEDED(voice_->Stop())) if (SUCCEEDED(voice->Stop()))
playing_ = false; playing_ = false;
} }
void Sound::Resume() void Sound::Resume()
{ {
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
if (SUCCEEDED(voice_->Start())) if (SUCCEEDED(voice->Start()))
playing_ = true; playing_ = true;
} }
void Sound::Stop() void Sound::Stop()
{ {
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
HRESULT hr = voice_->Stop(); HRESULT hr = voice->Stop();
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
hr = voice_->ExitLoop(); hr = voice->ExitLoop();
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
hr = voice_->FlushSourceBuffers(); hr = voice->FlushSourceBuffers();
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
playing_ = false; playing_ = false;
@ -187,16 +213,15 @@ void Sound::Stop()
void Sound::Close() void Sound::Close()
{ {
if (voice_) auto voice = GetNativePtr<IXAudio2SourceVoice>();
if (voice)
{ {
voice_->Stop(); voice->Stop();
voice_->FlushSourceBuffers(); voice->FlushSourceBuffers();
voice_->DestroyVoice(); voice->DestroyVoice();
voice_ = nullptr;
} }
transcoder_.ClearBuffer(); coder_ = nullptr;
opened_ = false; opened_ = false;
playing_ = false; playing_ = false;
} }
@ -205,14 +230,15 @@ bool Sound::IsPlaying() const
{ {
if (opened_) if (opened_)
{ {
if (!voice_)
return false;
if (!playing_) if (!playing_)
return false; return false;
auto voice = GetNativePtr<IXAudio2SourceVoice>();
if (!voice)
return false;
XAUDIO2_VOICE_STATE state; XAUDIO2_VOICE_STATE state;
voice_->GetState(&state); voice->GetState(&state);
return !!state.BuffersQueued; return !!state.BuffersQueued;
} }
return false; return false;
@ -220,19 +246,21 @@ bool Sound::IsPlaying() const
float Sound::GetVolume() const float Sound::GetVolume() const
{ {
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
float volume = 0.0f; float volume = 0.0f;
voice_->GetVolume(&volume); voice->GetVolume(&volume);
return volume; return volume;
} }
void Sound::SetVolume(float volume) void Sound::SetVolume(float volume)
{ {
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL"); auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
volume = std::min(std::max(volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL); volume = std::min(std::max(volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL);
voice_->SetVolume(volume); voice->SetVolume(volume);
} }
} // namespace audio } // namespace audio
} // namespace kiwano } // namespace kiwano

View File

@ -21,7 +21,7 @@
#pragma once #pragma once
#include <kiwano-audio/Transcoder.h> #include <kiwano-audio/Transcoder.h>
#include <kiwano/core/Resource.h> #include <kiwano/core/Resource.h>
#include <kiwano/base/ObjectBase.h> #include <kiwano/platform/NativeObject.hpp>
#include <xaudio2.h> #include <xaudio2.h>
namespace kiwano namespace kiwano
@ -41,11 +41,19 @@ KGE_DECLARE_SMART_PTR(Sound);
* \~chinese * \~chinese
* @brief * @brief
*/ */
class KGE_API Sound : public ObjectBase class KGE_API Sound : public NativeObject
{ {
friend class AudioModule; friend class AudioModule;
public: public:
/// \~chinese
/// @brief Ô¤¼ÓÔØÒôƵ
static SoundPtr Preload(const String& file_path);
/// \~chinese
/// @brief Ô¤¼ÓÔØÒôƵ×ÊÔ´
static SoundPtr Preload(const Resource& res);
/// \~chinese /// \~chinese
/// @brief 创建音频对象 /// @brief 创建音频对象
/// @param res 本地音频文件路径 /// @param res 本地音频文件路径
@ -71,8 +79,9 @@ public:
bool Load(const Resource& res); bool Load(const Resource& res);
/// \~chinese /// \~chinese
/// @brief ÊÇ·ñÓÐЧ /// @brief ´ò¿ªÒôƵ×ÊÔ´
bool IsValid() const; /// @param res ÒôƵ×ÊÔ´
bool Load(TranscoderPtr transcoder);
/// \~chinese /// \~chinese
/// @brief 播放 /// @brief 播放
@ -109,27 +118,12 @@ public:
void SetVolume(float volume); void SetVolume(float volume);
private: private:
IXAudio2SourceVoice* GetXAudio2Voice() const; bool opened_;
bool playing_;
void SetXAudio2Voice(IXAudio2SourceVoice* voice); TranscoderPtr coder_;
private:
bool opened_;
bool playing_;
Transcoder transcoder_;
IXAudio2SourceVoice* voice_;
}; };
/** @} */ /** @} */
inline IXAudio2SourceVoice* Sound::GetXAudio2Voice() const
{
return voice_;
}
inline void Sound::SetXAudio2Voice(IXAudio2SourceVoice* voice)
{
voice_ = voice;
}
} // namespace audio } // namespace audio
} // namespace kiwano } // namespace kiwano

View File

@ -20,6 +20,7 @@
#pragma once #pragma once
#include <kiwano/core/Resource.h> #include <kiwano/core/Resource.h>
#include <kiwano/base/ObjectBase.h>
#include <mfapi.h> #include <mfapi.h>
#include <mfidl.h> #include <mfidl.h>
#include <mfreadwrite.h> #include <mfreadwrite.h>
@ -28,7 +29,9 @@ namespace kiwano
{ {
namespace audio namespace audio
{ {
class Sound; class AudioModule;
KGE_DECLARE_SMART_PTR(Transcoder);
/** /**
* \addtogroup Audio * \addtogroup Audio
@ -39,9 +42,9 @@ class Sound;
* \~chinese * \~chinese
* @brief * @brief
*/ */
class KGE_API Transcoder class KGE_API Transcoder : public ObjectBase
{ {
friend class Sound; friend class AudioModule;
public: public:
/** /**
@ -86,6 +89,56 @@ private:
WAVEFORMATEX* wave_format_; WAVEFORMATEX* wave_format_;
}; };
class KGE_API TranscoderCache final : public Singleton<TranscoderCache>
{
friend Singleton<TranscoderCache>;
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<size_t, TranscoderPtr> cache_;
};
/** @} */ /** @} */
} // namespace audio } // namespace audio
} // namespace kiwano } // namespace kiwano