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/core/Exception.h>
#include <kiwano/utils/Logger.h>
#include <kiwano/platform/FileSystem.h>
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<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!");
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);
}
}

View File

@ -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;

View File

@ -21,12 +21,50 @@
#include <kiwano-audio/AudioModule.h>
#include <kiwano-audio/Sound.h>
#include <kiwano/utils/Logger.h>
#include <kiwano/platform/FileSystem.h>
namespace kiwano
{
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()
{
@ -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<IXAudio2SourceVoice>();
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<uint32_t>(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<uint32_t>(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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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

View File

@ -21,7 +21,7 @@
#pragma once
#include <kiwano-audio/Transcoder.h>
#include <kiwano/core/Resource.h>
#include <kiwano/base/ObjectBase.h>
#include <kiwano/platform/NativeObject.hpp>
#include <xaudio2.h>
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

View File

@ -20,6 +20,7 @@
#pragma once
#include <kiwano/core/Resource.h>
#include <kiwano/base/ObjectBase.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
@ -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<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 kiwano