pref: add sound transcoder cache
This commit is contained in:
parent
03c24091c7
commit
c061c8c802
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue