feat: add sound callback

This commit is contained in:
Nomango 2023-09-25 00:15:18 +08:00
parent c061c8c802
commit 88c758b47c
3 changed files with 330 additions and 34 deletions

View File

@ -29,6 +29,42 @@ namespace kiwano
namespace audio
{
class VoiceCallback : public IXAudio2VoiceCallback
{
public:
SoundCallback* cb;
VoiceCallback(SoundCallback* cb)
: cb(cb)
{
}
~VoiceCallback() {}
STDMETHOD_(void, OnBufferStart(void* pBufferContext))
{
cb->OnStart(nullptr);
}
STDMETHOD_(void, OnLoopEnd(void* pBufferContext))
{
cb->OnLoopEnd(nullptr);
}
STDMETHOD_(void, OnBufferEnd(void* pBufferContext))
{
cb->OnEnd(nullptr);
}
STDMETHOD_(void, OnStreamEnd()) {}
STDMETHOD_(void, OnVoiceProcessingPassEnd()) {}
STDMETHOD_(void, OnVoiceProcessingPassStart(UINT32 SamplesRequired)) {}
STDMETHOD_(void, OnVoiceError(void* pBufferContext, HRESULT Error)) {}
};
AudioModule::AudioModule()
: x_audio2_(nullptr)
, mastering_voice_(nullptr)
@ -121,10 +157,12 @@ bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder)
if (SUCCEEDED(hr))
{
auto chain = sound.GetCallbackChain();
chain->SetNative(VoiceCallback{ chain.Get() });
auto callback = const_cast<VoiceCallback*>(chain->GetNative().CastPtr<VoiceCallback>());
IXAudio2SourceVoice* voice = nullptr;
hr = x_audio2_->CreateSourceVoice(&voice, buffer.format, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
hr = x_audio2_->CreateSourceVoice(&voice, buffer.format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback);
if (SUCCEEDED(hr))
{
sound.Close();
@ -143,7 +181,6 @@ bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder)
void AudioModule::Open()
{
KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!");
if (x_audio2_)
x_audio2_->StartEngine();
}

View File

@ -27,9 +27,13 @@ namespace kiwano
namespace audio
{
SoundPtr Sound::Preload(const String& file_path)
SoundPtr Sound::Preload(const String& file_path, std::initializer_list<SoundCallbackPtr> callbacks)
{
auto ptr = MakePtr<Sound>();
for (auto& cb : callbacks)
{
ptr->AddCallback(cb);
}
size_t hash_code = std::hash<String>{}(file_path);
if (TranscoderPtr transcoder = TranscoderCache::GetInstance().Get(hash_code))
@ -46,9 +50,13 @@ SoundPtr Sound::Preload(const String& file_path)
return ptr;
}
SoundPtr Sound::Preload(const Resource& res)
SoundPtr Sound::Preload(const Resource& res, std::initializer_list<SoundCallbackPtr> callbacks)
{
auto ptr = MakePtr<Sound>();
for (auto& cb : callbacks)
{
ptr->AddCallback(cb);
}
size_t hash_code = res.GetId();
if (TranscoderPtr transcoder = TranscoderCache::GetInstance().Get(hash_code))
@ -80,6 +88,7 @@ Sound::Sound(const Resource& res)
Sound::Sound()
: opened_(false)
, playing_(false)
, volume_(1.f)
{
}
@ -129,6 +138,11 @@ bool Sound::Load(TranscoderPtr transcoder)
return false;
}
// reset volume
const float old_volume = volume_;
volume_ = 0.f;
SetVolume(old_volume);
coder_ = transcoder;
opened_ = true;
return true;
@ -181,8 +195,14 @@ void Sound::Pause()
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
if (SUCCEEDED(voice->Stop()))
HRESULT hr = voice->Stop();
if (SUCCEEDED(hr))
playing_ = false;
if (FAILED(hr))
{
KGE_ERRORF("Pause voice failed with HRESULT of %08X", hr);
}
}
void Sound::Resume()
@ -190,8 +210,14 @@ void Sound::Resume()
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
if (SUCCEEDED(voice->Start()))
HRESULT hr = voice->Start();
if (SUCCEEDED(hr))
playing_ = true;
if (FAILED(hr))
{
KGE_ERRORF("Start voice failed with HRESULT of %08X", hr);
}
}
void Sound::Stop()
@ -209,6 +235,11 @@ void Sound::Stop()
if (SUCCEEDED(hr))
playing_ = false;
if (FAILED(hr))
{
KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr);
}
}
void Sound::Close()
@ -246,21 +277,169 @@ bool Sound::IsPlaying() const
float Sound::GetVolume() const
{
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
float volume = 0.0f;
voice->GetVolume(&volume);
return volume;
return volume_;
}
void Sound::SetVolume(float volume)
{
if (volume_ == volume)
{
return;
}
volume_ = volume;
float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_);
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);
actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL);
voice->SetVolume(actual_volume);
}
SoundCallbackPtr Sound::GetCallbackChain()
{
class SoundCallbackChain : public SoundCallback
{
public:
Sound* sound;
void OnStart(Sound*) override
{
for (auto& cb : sound->GetCallbacks())
{
if (cb)
{
cb->OnStart(sound);
}
}
}
void OnLoopEnd(Sound*) override
{
for (auto& cb : sound->GetCallbacks())
{
if (cb)
{
cb->OnLoopEnd(sound);
}
}
}
void OnEnd(Sound*) override
{
for (auto& cb : sound->GetCallbacks())
{
if (cb)
{
cb->OnEnd(sound);
}
}
}
float OnVolumeChanged(Sound*, float volume) override
{
float actual_volume = volume;
for (auto& cb : sound->GetCallbacks())
{
if (cb)
{
actual_volume = cb->OnVolumeChanged(sound, volume);
}
}
return actual_volume;
}
};
if (!callback_chain_)
{
auto chain = MakePtr<SoundCallbackChain>();
chain->sound = this;
callback_chain_ = chain;
}
return callback_chain_;
}
SoundCallbackPtr SoundCallback::OnStart(const Function<void(Sound* sound)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<void(Sound* sound)> cb;
void OnStart(Sound* sound) override
{
if (cb)
{
cb(sound);
}
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
SoundCallbackPtr SoundCallback::OnLoopEnd(const Function<void(Sound* sound)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<void(Sound* sound)> cb;
void OnLoopEnd(Sound* sound) override
{
if (cb)
{
cb(sound);
}
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
SoundCallbackPtr SoundCallback::OnEnd(const Function<void(Sound* sound)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<void(Sound* sound)> cb;
void OnEnd(Sound* sound) override
{
if (cb)
{
cb(sound);
}
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
SoundCallbackPtr SoundCallback::OnVolumeChanged(const Function<float(Sound* sound, float volume)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<float(Sound* sound, float volume)> cb;
float OnVolumeChanged(Sound* sound, float volume) override
{
if (cb)
{
return cb(sound, volume);
}
return volume;
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
} // namespace audio
} // namespace kiwano

View File

@ -29,8 +29,10 @@ namespace kiwano
namespace audio
{
class AudioModule;
class SoundPlayer;
KGE_DECLARE_SMART_PTR(Sound);
KGE_DECLARE_SMART_PTR(SoundCallback);
/**
* \addtogroup Audio
@ -39,20 +41,64 @@ KGE_DECLARE_SMART_PTR(Sound);
/**
* \~chinese
* @brief
* @brief
*/
class KGE_API SoundCallback : public NativeObject
{
public:
/// \~chinese
/// @brief 创建一个回调,在音频开始播放时执行
static SoundCallbackPtr OnStart(const Function<void(Sound* sound)>& cb);
/// \~chinese
/// @brief 创建一个回调,在音频循环结束时执行
static SoundCallbackPtr OnLoopEnd(const Function<void(Sound* sound)>& cb);
/// \~chinese
/// @brief 创建一个回调,在音频结束时执行
static SoundCallbackPtr OnEnd(const Function<void(Sound* sound)>& cb);
/// \~chinese
/// @brief 创建一个回调,在音频修改音量时执行
static SoundCallbackPtr OnVolumeChanged(const Function<float(Sound* sound, float volume)>& cb);
/// \~chinese
/// @brief 在音频开始播放时执行
virtual inline void OnStart(Sound* sound) {}
/// \~chinese
/// @brief 在音频循环结束时执行
virtual inline void OnLoopEnd(Sound* sound) {}
/// \~chinese
/// @brief 在音频结束时执行
virtual inline void OnEnd(Sound* sound) {}
/// \~chinese
/// @brief 在音频修改音量时执行
virtual inline float OnVolumeChanged(Sound* sound, float volume)
{
return volume;
}
};
/**
* \~chinese
* @brief
*/
class KGE_API Sound : public NativeObject
{
friend class AudioModule;
friend class SoundPlayer;
public:
/// \~chinese
/// @brief 预加载音频
static SoundPtr Preload(const String& file_path);
static SoundPtr Preload(const String& file_path, std::initializer_list<SoundCallbackPtr> callbacks = {});
/// \~chinese
/// @brief 预加载音频资源
static SoundPtr Preload(const Resource& res);
static SoundPtr Preload(const Resource& res, std::initializer_list<SoundCallbackPtr> callbacks = {});
/// \~chinese
/// @brief 创建音频对象
@ -68,21 +114,6 @@ public:
virtual ~Sound();
/// \~chinese
/// @brief 打开本地音频文件
/// @param res 本地音频文件路径
bool Load(const String& file_path);
/// \~chinese
/// @brief 打开音频资源
/// @param res 音频资源
bool Load(const Resource& res);
/// \~chinese
/// @brief 打开音频资源
/// @param res 音频资源
bool Load(TranscoderPtr transcoder);
/// \~chinese
/// @brief 播放
/// @param loop_count 播放循环次数,设置 -1 为循环播放
@ -117,13 +148,62 @@ public:
/// @param volume 音量大小1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量
void SetVolume(float volume);
/// \~chinese
/// @brief 添加回调
void AddCallback(SoundCallbackPtr callback);
/// \~chinese
/// @brief 获取所有回调
List<SoundCallbackPtr>& GetCallbacks();
/// \~chinese
/// @brief 获取所有回调
const List<SoundCallbackPtr>& GetCallbacks() const;
protected:
/// \~chinese
/// @brief 打开本地音频文件
/// @param res 本地音频文件路径
bool Load(const String& file_path);
/// \~chinese
/// @brief 打开音频资源
/// @param res 音频资源
bool Load(const Resource& res);
/// \~chinese
/// @brief 打开音频资源
/// @param res 音频资源
bool Load(TranscoderPtr transcoder);
SoundCallbackPtr GetCallbackChain();
private:
bool opened_;
bool playing_;
float volume_;
TranscoderPtr coder_;
SoundCallbackPtr callback_chain_;
List<SoundCallbackPtr> callbacks_;
};
/** @} */
inline List<SoundCallbackPtr>& kiwano::audio::Sound::GetCallbacks()
{
return callbacks_;
}
inline const List<SoundCallbackPtr>& kiwano::audio::Sound::GetCallbacks() const
{
return callbacks_;
}
inline void kiwano::audio::Sound::AddCallback(SoundCallbackPtr callback)
{
callbacks_.push_back(callback);
}
} // namespace audio
} // namespace kiwano