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 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() AudioModule::AudioModule()
: x_audio2_(nullptr) : x_audio2_(nullptr)
, mastering_voice_(nullptr) , mastering_voice_(nullptr)
@ -121,10 +157,12 @@ bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder)
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
auto chain = sound.GetCallbackChain();
chain->SetNative(VoiceCallback{ chain.Get() });
auto callback = const_cast<VoiceCallback*>(chain->GetNative().CastPtr<VoiceCallback>());
IXAudio2SourceVoice* voice = nullptr; IXAudio2SourceVoice* voice = nullptr;
hr = x_audio2_->CreateSourceVoice(&voice, buffer.format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback);
hr = x_audio2_->CreateSourceVoice(&voice, buffer.format, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
sound.Close(); sound.Close();
@ -143,7 +181,6 @@ bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder)
void AudioModule::Open() void AudioModule::Open()
{ {
KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!"); KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!");
if (x_audio2_) if (x_audio2_)
x_audio2_->StartEngine(); x_audio2_->StartEngine();
} }

View File

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

View File

@ -29,8 +29,10 @@ namespace kiwano
namespace audio namespace audio
{ {
class AudioModule; class AudioModule;
class SoundPlayer;
KGE_DECLARE_SMART_PTR(Sound); KGE_DECLARE_SMART_PTR(Sound);
KGE_DECLARE_SMART_PTR(SoundCallback);
/** /**
* \addtogroup Audio * \addtogroup Audio
@ -39,20 +41,64 @@ KGE_DECLARE_SMART_PTR(Sound);
/** /**
* \~chinese * \~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 class KGE_API Sound : public NativeObject
{ {
friend class AudioModule; friend class AudioModule;
friend class SoundPlayer;
public: public:
/// \~chinese /// \~chinese
/// @brief 预加载音频 /// @brief 预加载音频
static SoundPtr Preload(const String& file_path); static SoundPtr Preload(const String& file_path, std::initializer_list<SoundCallbackPtr> callbacks = {});
/// \~chinese /// \~chinese
/// @brief 预加载音频资源 /// @brief 预加载音频资源
static SoundPtr Preload(const Resource& res); static SoundPtr Preload(const Resource& res, std::initializer_list<SoundCallbackPtr> callbacks = {});
/// \~chinese /// \~chinese
/// @brief 创建音频对象 /// @brief 创建音频对象
@ -68,21 +114,6 @@ public:
virtual ~Sound(); 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 /// \~chinese
/// @brief 播放 /// @brief 播放
/// @param loop_count 播放循环次数,设置 -1 为循环播放 /// @param loop_count 播放循环次数,设置 -1 为循环播放
@ -117,13 +148,62 @@ public:
/// @param volume 音量大小1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量 /// @param volume 音量大小1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量
void SetVolume(float volume); 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: private:
bool opened_; bool opened_;
bool playing_; bool playing_;
float volume_;
TranscoderPtr coder_; 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 audio
} // namespace kiwano } // namespace kiwano