diff --git a/projects/kiwano-audio/kiwano-audio.vcxproj b/projects/kiwano-audio/kiwano-audio.vcxproj index 256809b1..f847e71e 100644 --- a/projects/kiwano-audio/kiwano-audio.vcxproj +++ b/projects/kiwano-audio/kiwano-audio.vcxproj @@ -1,9 +1,12 @@ - + + + + @@ -27,11 +30,13 @@ - + + + + - {1B97937D-8184-426C-BE71-29A163DC76C9} @@ -184,4 +189,4 @@ - + \ No newline at end of file diff --git a/projects/kiwano-audio/kiwano-audio.vcxproj.filters b/projects/kiwano-audio/kiwano-audio.vcxproj.filters index f3d328d0..6ee09c2d 100644 --- a/projects/kiwano-audio/kiwano-audio.vcxproj.filters +++ b/projects/kiwano-audio/kiwano-audio.vcxproj.filters @@ -5,14 +5,32 @@ - + + + MediaFoundation + + + + MediaFoundation + - - + + + MediaFoundation + + + + MediaFoundation + + + + + {213a58dc-8054-4bc7-bac5-456e889ae038} + \ No newline at end of file diff --git a/src/kiwano-audio/AudioData.cpp b/src/kiwano-audio/AudioData.cpp new file mode 100644 index 00000000..d065acf8 --- /dev/null +++ b/src/kiwano-audio/AudioData.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include + +namespace kiwano +{ +namespace audio +{ +AudioData::AudioData(const BinaryData& data, const AudioMeta& meta) + : data_(data) + , meta_(meta) +{ +} + +AudioMeta AudioData::GetMeta() const +{ + return meta_; +} + +BinaryData AudioData::GetData() const +{ + return data_; +} + +} // namespace audio +} // namespace kiwano diff --git a/src/kiwano-audio/AudioData.h b/src/kiwano-audio/AudioData.h new file mode 100644 index 00000000..f8c9c17c --- /dev/null +++ b/src/kiwano-audio/AudioData.h @@ -0,0 +1,92 @@ +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once +#include +#include +#include + +namespace kiwano +{ +namespace audio +{ + +KGE_DECLARE_SMART_PTR(AudioData); + +/** + * \addtogroup Audio + * @{ + */ + +/** + * \~chinese + * @brief 音频格式 + */ +enum class AudioFormat +{ + PCM, +}; + +/** + * \~chinese + * @brief 音频元数据 + */ +struct AudioMeta +{ + AudioFormat format = AudioFormat::PCM; ///< 音频格式 + uint16_t channels = 2; ///< 声道,单声道为1,立体声为2 + uint32_t samples_per_sec = 44100; ///< 采样率,11025 表示 11.025kHz。PCM格式的采样率通常为44.1kHz + uint16_t bits_per_sample = 16; ///< 位深,PCM格式为 8 或 16 + uint16_t block_align = 4; ///< 块对齐,PCM格式通常是 (channels * bits_per_sample) / 8 +}; + +/** + * \~chinese + * @brief 音频数据 + */ +class AudioData : public NativeObject +{ +public: + /// \~chinese + /// @brief 音频数据 + /// @param data 音频数据 + /// @param meta 音频元数据 + AudioData(const BinaryData& data, const AudioMeta& meta); + + /// \~chinese + /// @brief 获取音频元数据 + AudioMeta GetMeta() const; + + /// \~chinese + /// @brief 获取数据 + BinaryData GetData() const; + +protected: + AudioData() = default; + +protected: + BinaryData data_; + AudioMeta meta_; +}; + +/** @} */ + +} // namespace audio +} // namespace kiwano diff --git a/src/kiwano-audio/AudioModule.cpp b/src/kiwano-audio/AudioModule.cpp index 9298a569..77d112a3 100644 --- a/src/kiwano-audio/AudioModule.cpp +++ b/src/kiwano-audio/AudioModule.cpp @@ -17,11 +17,12 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - -#include -#include #include #include +#include +#include +#include +#include namespace kiwano { @@ -89,16 +90,16 @@ void AudioModule::SetupModule() { KGE_DEBUG_LOGF("Creating audio resources"); - HRESULT hr = dlls::MediaFoundation::Get().MFStartup(MF_VERSION, MFSTARTUP_FULL); - - if (SUCCEEDED(hr)) - { - hr = dlls::XAudio2::Get().XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR); - } + HRESULT hr = dlls::XAudio2::Get().XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR); if (SUCCEEDED(hr)) { hr = x_audio2_->CreateMasteringVoice(&mastering_voice_); + } + + if (SUCCEEDED(hr)) + { + RegisterTranscoder("*", MakePtr()); } KGE_THROW_IF_FAILED(hr, "Create audio resources failed"); @@ -119,28 +120,28 @@ void AudioModule::DestroyModule() x_audio2_->Release(); x_audio2_ = nullptr; } - - dlls::MediaFoundation::Get().MFShutdown(); } -bool AudioModule::CreateSound(Sound& sound, const AudioMetadata& metadata) +bool AudioModule::CreateSound(Sound& sound, AudioDataPtr data) { KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!"); - HRESULT hr = S_OK; - - WAVEFORMATEX wave_format = { 0 }; - wave_format.wFormatTag = ConvertWaveFormat(metadata.format); - wave_format.nChannels = WORD(metadata.channels); - wave_format.nSamplesPerSec = DWORD(metadata.samples_per_sec); - wave_format.wBitsPerSample = WORD(metadata.bits_per_sample); - wave_format.nBlockAlign = WORD(metadata.block_align); - wave_format.nAvgBytesPerSec = DWORD(metadata.samples_per_sec * metadata.block_align); + HRESULT hr = S_OK; - WAVEFORMATEX* wave_format_ptr = &wave_format; - if (metadata.extra_data != nullptr) + WAVEFORMATEX* wave_fmt = data->GetNative(); + if (wave_fmt == nullptr) { - wave_format_ptr = reinterpret_cast(metadata.extra_data); + const auto meta = data->GetMeta(); + WAVEFORMATEX tmp = { 0 }; + tmp.wFormatTag = ConvertWaveFormat(meta.format); + tmp.nChannels = WORD(meta.channels); + tmp.nSamplesPerSec = DWORD(meta.samples_per_sec); + tmp.wBitsPerSample = WORD(meta.bits_per_sample); + tmp.nBlockAlign = WORD(meta.block_align); + tmp.nAvgBytesPerSec = DWORD(meta.samples_per_sec * meta.block_align); + + data->SetNative(tmp); + wave_fmt = const_cast(data->GetNative().CastPtr()); } if (SUCCEEDED(hr)) @@ -150,7 +151,7 @@ bool AudioModule::CreateSound(Sound& sound, const AudioMetadata& metadata) auto callback = const_cast(chain->GetNative().CastPtr()); IXAudio2SourceVoice* voice = nullptr; - hr = x_audio2_->CreateSourceVoice(&voice, wave_format_ptr, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback); + hr = x_audio2_->CreateSourceVoice(&voice, wave_fmt, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback); if (SUCCEEDED(hr)) { sound.Close(); @@ -179,6 +180,51 @@ void AudioModule::Close() if (x_audio2_) x_audio2_->StopEngine(); +} + +void AudioModule::RegisterTranscoder(const String& ext, TranscoderPtr transcoder) +{ + registered_transcoders_.insert(std::make_pair(ext, transcoder)); } + +TranscoderPtr AudioModule::GetTranscoder(const String& ext) +{ + auto iter = registered_transcoders_.find(ext); + if (iter != registered_transcoders_.end()) + { + return iter->second; + } + return registered_transcoders_.at("*"); +} + +AudioDataPtr AudioModule::Decode(const String& file_path) +{ + if (!FileSystem::GetInstance().IsFileExists(file_path)) + { + KGE_WARNF("Media file '%s' not found", file_path.c_str()); + return nullptr; + } + + const auto ext = FileSystem::GetInstance().GetFileExt(file_path); + + auto transcoder = GetTranscoder(ext); + if (!transcoder) + { + return nullptr; + } + String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path); + return transcoder->Decode(full_path); +} + +AudioDataPtr AudioModule::Decode(const Resource& res, const String& ext) +{ + auto transcoder = GetTranscoder(ext); + if (!transcoder) + { + return nullptr; + } + return transcoder->Decode(res); +} + } // namespace audio } // namespace kiwano diff --git a/src/kiwano-audio/AudioModule.h b/src/kiwano-audio/AudioModule.h index 54f36147..0d772922 100644 --- a/src/kiwano-audio/AudioModule.h +++ b/src/kiwano-audio/AudioModule.h @@ -59,9 +59,30 @@ public: /// @brief 关闭音频设备 void Close(); + /// \~chinese + /// @brief 注册解码器 + /// @param ext 文件类型(如:ogg),* 为默认解码器 + /// @param transcoder 解码器 + void RegisterTranscoder(const String& ext, TranscoderPtr transcoder); + + /// \~chinese + /// @brief 获取解码器 + TranscoderPtr GetTranscoder(const String& ext); + + /// \~chinese + /// @brief 解码音频 + /// @param file_path 本地音频文件路径 + AudioDataPtr Decode(const String& file_path); + + /// \~chinese + /// @brief 解码音频 + /// @param res 音频资源 + /// @param ext 音频类型,决定了使用何种解码器 + AudioDataPtr Decode(const Resource& res, const String& ext = ""); + /// \~chinese /// @brief 创建音频 - bool CreateSound(Sound& sound, const AudioMetadata& metadata); + bool CreateSound(Sound& sound, AudioDataPtr data); public: void SetupModule() override; @@ -76,6 +97,8 @@ private: private: IXAudio2* x_audio2_; IXAudio2MasteringVoice* mastering_voice_; + + UnorderedMap registered_transcoders_; }; /** @} */ diff --git a/src/kiwano-audio/Transcoder.cpp b/src/kiwano-audio/MediaFoundation/Transcoder.cpp similarity index 67% rename from src/kiwano-audio/Transcoder.cpp rename to src/kiwano-audio/MediaFoundation/Transcoder.cpp index db0218eb..b4f7b4bb 100644 --- a/src/kiwano-audio/Transcoder.cpp +++ b/src/kiwano-audio/MediaFoundation/Transcoder.cpp @@ -18,113 +18,53 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#ifndef INITGUID -#define INITGUID // MFAudioFormat_PCM, MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE, MFMediaType_Audio -#endif - -#include -#include +#include +#include #include -#include #include #include #include -#include -#include -#include -#include namespace kiwano { namespace audio -{ - -HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std::unique_ptr& output_data, - uint32_t& output_size); - -Transcoder::Transcoder(const String& file_path) { - Load(file_path); -} - -Transcoder::Transcoder(const Resource& res) -{ - Load(res); -} - -Transcoder::Transcoder(const BinaryData& data, const AudioMetadata& metadata) -{ - Load(data, metadata); -} -Transcoder::~Transcoder() +HRESULT ReadSource(AudioDataPtr& output, IMFSourceReader* reader); + +MFTranscoder::MFTranscoder() { - Clear(); -} - -AudioMetadata Transcoder::GetMetadata() const -{ - return metadata_; + HRESULT hr = dlls::MediaFoundation::Get().MFStartup(MF_VERSION, MFSTARTUP_FULL); + KGE_THROW_IF_FAILED(hr, "Initialize Media Foundation failed!"); } -BinaryData Transcoder::GetData() const +MFTranscoder::~MFTranscoder() { - return data_; + dlls::MediaFoundation::Get().MFShutdown(); } -void Transcoder::Clear() +AudioDataPtr MFTranscoder::Decode(const String& file_path) { - if (metadata_.extra_data != nullptr) - { - WAVEFORMATEX* wave_format = reinterpret_cast(metadata_.extra_data); - ::CoTaskMemFree(wave_format); - metadata_.extra_data = nullptr; - } - - data_ = {}; - raw_.reset(); -} + ComPtr reader; -bool Transcoder::Load(const BinaryData& data, const AudioMetadata& metadata) -{ - data_ = data; - metadata_ = metadata; - return true; -} + WideString path = strings::NarrowToWide(file_path); + HRESULT hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromURL(path.c_str(), nullptr, &reader); -bool Transcoder::Load(const String& file_path) -{ - if (!FileSystem::GetInstance().IsFileExists(file_path)) - { - KGE_WARNF("Media file '%s' not found", file_path.c_str()); - return false; - } - - String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path); - WideString path = strings::NarrowToWide(full_path); - - ComPtr reader; - - HRESULT hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromURL(path.c_str(), nullptr, &reader); + AudioDataPtr output; if (SUCCEEDED(hr)) { - hr = ReadSource(reader.Get(), metadata_, raw_, data_.size); - - if (SUCCEEDED(hr)) - { - data_.buffer = raw_.get(); - } + hr = ReadSource(output, reader.Get()); } - - if (FAILED(hr)) + + if (FAILED(hr)) { Fail(strings::Format("%s failed (%#x): %s", __FUNCTION__, hr, "Load audio failed")); - return false; + return nullptr; } - return true; + return output; } -bool Transcoder::Load(const Resource& res) +AudioDataPtr MFTranscoder::Decode(const Resource& res) { HRESULT hr = S_OK; @@ -136,7 +76,7 @@ bool Transcoder::Load(const Resource& res) if (!data.IsValid()) { Fail("invalid audio data"); - return false; + return nullptr; } stream = win32::dlls::Shlwapi::Get().SHCreateMemStream(static_cast(data.buffer), @@ -145,7 +85,7 @@ bool Transcoder::Load(const Resource& res) if (stream == nullptr) { Fail("SHCreateMemStream failed"); - return false; + return nullptr; } if (SUCCEEDED(hr)) @@ -158,26 +98,51 @@ bool Transcoder::Load(const Resource& res) hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromByteStream(byte_stream.Get(), nullptr, &reader); } + AudioDataPtr output; if (SUCCEEDED(hr)) { - hr = ReadSource(reader.Get(), metadata_, raw_, data_.size); - - if (SUCCEEDED(hr)) - { - data_.buffer = raw_.get(); - } + hr = ReadSource(output, reader.Get()); } if (FAILED(hr)) { Fail(strings::Format("%s failed (%#x): %s", __FUNCTION__, hr, "Load audio failed")); - return false; + return nullptr; } - return true; -} + return output; +} -HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std::unique_ptr& output_data, - uint32_t& output_size) +class MFAudioData : public AudioData +{ +public: + MFAudioData(WAVEFORMATEX* wave_fmt, std::unique_ptr raw, uint32_t size) + : wave_fmt_(wave_fmt) + , raw_(std::move(raw)) + { + data_ = BinaryData{ raw_.get(), size }; + meta_.format = AudioFormat::PCM; + meta_.channels = uint16_t(wave_fmt->nChannels); + meta_.samples_per_sec = uint32_t(wave_fmt->nSamplesPerSec); + meta_.bits_per_sample = uint16_t(wave_fmt->wBitsPerSample); + meta_.block_align = uint16_t(wave_fmt->nBlockAlign); + + SetNative(wave_fmt); + } + + virtual ~MFAudioData() + { + if (wave_fmt_ != nullptr) + { + ::CoTaskMemFree(wave_fmt_); + wave_fmt_ = nullptr; + } + } + + WAVEFORMATEX* wave_fmt_; + std::unique_ptr raw_; +}; + +HRESULT ReadSource(AudioDataPtr& output, IMFSourceReader* reader) { HRESULT hr = S_OK; @@ -224,16 +189,6 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std: uncompressed_type.Get(), &wave_format, &size, (DWORD)MFWaveFormatExConvertFlag_Normal); } - if (SUCCEEDED(hr)) - { - output_metadata.format = AudioFormat::PCM; - output_metadata.channels = uint16_t(wave_format->nChannels); - output_metadata.samples_per_sec = uint32_t(wave_format->nSamplesPerSec); - output_metadata.bits_per_sample = uint16_t(wave_format->wBitsPerSample); - output_metadata.block_align = uint16_t(wave_format->nBlockAlign); - output_metadata.extra_data = wave_format; - } - // 估算音频流大小 DWORD max_stream_size = 0; if (SUCCEEDED(hr)) @@ -241,9 +196,9 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std: PROPVARIANT prop; PropVariantInit(&prop); - hr = reader->GetPresentationAttribute((DWORD)MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &prop); - - if (SUCCEEDED(hr)) + hr = reader->GetPresentationAttribute((DWORD)MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &prop); + + if (SUCCEEDED(hr)) { LONGLONG duration = prop.uhVal.QuadPart; max_stream_size = static_cast((duration * wave_format->nAvgBytesPerSec) / 10000000 + 1); @@ -255,9 +210,9 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std: if (SUCCEEDED(hr)) { DWORD flags = 0; - DWORD position = 0; - - output_data = std::make_unique(max_stream_size); + DWORD position = 0; + + auto output_data = std::make_unique(max_stream_size); ComPtr sample; ComPtr buffer; @@ -319,12 +274,8 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std: if (SUCCEEDED(hr)) { - output_size = uint32_t(position); - } - else - { - output_data.reset(); - output_size = 0; + output = new MFAudioData(wave_format, std::move(output_data), uint32_t(position)); + return hr; } } } diff --git a/src/kiwano-audio/MediaFoundation/Transcoder.h b/src/kiwano-audio/MediaFoundation/Transcoder.h new file mode 100644 index 00000000..b8844d7f --- /dev/null +++ b/src/kiwano-audio/MediaFoundation/Transcoder.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once +#include + +namespace kiwano +{ +namespace audio +{ + +/** + * \addtogroup Audio + * @{ + */ + +/** + * \~chinese + * @brief Media Foundation 音频解码器 + * @detail 支持 .wav .mp3 等常见音频类型 + */ +class KGE_API MFTranscoder : public Transcoder +{ +public: + MFTranscoder(); + + virtual ~MFTranscoder(); + + AudioDataPtr Decode(const String& file_path) override; + + AudioDataPtr Decode(const Resource& res) override; +}; + +/** @} */ + +} // namespace audio +} // namespace kiwano diff --git a/src/kiwano-audio/MediaFoundation/mflib.cpp b/src/kiwano-audio/MediaFoundation/mflib.cpp new file mode 100644 index 00000000..99e204b9 --- /dev/null +++ b/src/kiwano-audio/MediaFoundation/mflib.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#include +#include + +namespace kiwano +{ +namespace audio +{ +namespace dlls +{ +MediaFoundation::MediaFoundation() + : mfplat() + , mfreadwrite() + , MFStartup(nullptr) + , MFShutdown(nullptr) + , MFCreateMediaType(nullptr) + , MFCreateWaveFormatExFromMFMediaType(nullptr) + , MFCreateSourceReaderFromURL(nullptr) + , MFCreateSourceReaderFromByteStream(nullptr) + , MFCreateMFByteStreamOnStream(nullptr) +{ + if (mfplat.Load("Mfplat.dll")) + { + MFStartup = mfplat.GetProcess("MFStartup"); + MFShutdown = mfplat.GetProcess("MFShutdown"); + MFCreateMediaType = mfplat.GetProcess("MFCreateMediaType"); + MFCreateWaveFormatExFromMFMediaType = + mfplat.GetProcess("MFCreateWaveFormatExFromMFMediaType"); + MFCreateMFByteStreamOnStream = + mfplat.GetProcess("MFCreateMFByteStreamOnStream"); + } + else + { + KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfplat.dll failed"); + } + + if (mfreadwrite.Load("Mfreadwrite.dll")) + { + MFCreateSourceReaderFromURL = + mfreadwrite.GetProcess("MFCreateSourceReaderFromURL"); + MFCreateSourceReaderFromByteStream = + mfreadwrite.GetProcess("MFCreateSourceReaderFromByteStream"); + } + else + { + KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfreadwrite.dll failed"); + } +} +} // namespace dlls +} // namespace audio +} // namespace kiwano diff --git a/src/kiwano-audio/MediaFoundation/mflib.h b/src/kiwano-audio/MediaFoundation/mflib.h new file mode 100644 index 00000000..ada1e1aa --- /dev/null +++ b/src/kiwano-audio/MediaFoundation/mflib.h @@ -0,0 +1,79 @@ +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#ifndef INITGUID +#define INITGUID // MFAudioFormat_PCM, MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE, MFMediaType_Audio +#endif + +#include +#include +#include +#include + +#ifndef KGE_DOXYGEN_DO_NOT_INCLUDE + +namespace kiwano +{ +namespace audio +{ +namespace dlls +{ +class KGE_API MediaFoundation +{ +public: + static inline MediaFoundation& Get() + { + static MediaFoundation instance; + return instance; + } + + // MediaFoundation functions + typedef HRESULT(WINAPI* PFN_MFStartup)(ULONG, DWORD); + typedef HRESULT(WINAPI* PFN_MFShutdown)(); + typedef HRESULT(WINAPI* PFN_MFCreateMediaType)(IMFMediaType**); + typedef HRESULT(WINAPI* PFN_MFCreateWaveFormatExFromMFMediaType)(IMFMediaType*, WAVEFORMATEX**, UINT32*, UINT32); + typedef HRESULT(WINAPI* PFN_MFCreateSourceReaderFromURL)(LPCWSTR, IMFAttributes*, IMFSourceReader**); + typedef HRESULT(WINAPI* PFN_MFCreateSourceReaderFromByteStream)(IMFByteStream*, IMFAttributes*, IMFSourceReader**); + typedef HRESULT(WINAPI* PFN_MFCreateMFByteStreamOnStream)(IStream*, IMFByteStream**); + + PFN_MFStartup MFStartup; + PFN_MFShutdown MFShutdown; + PFN_MFCreateMediaType MFCreateMediaType; + PFN_MFCreateWaveFormatExFromMFMediaType MFCreateWaveFormatExFromMFMediaType; + PFN_MFCreateSourceReaderFromURL MFCreateSourceReaderFromURL; + PFN_MFCreateSourceReaderFromByteStream MFCreateSourceReaderFromByteStream; + PFN_MFCreateMFByteStreamOnStream MFCreateMFByteStreamOnStream; + +private: + MediaFoundation(); + + MediaFoundation(const MediaFoundation&) = delete; + MediaFoundation& operator=(const MediaFoundation&) = delete; + + Library mfplat; + Library mfreadwrite; +}; +} // namespace dlls +} // namespace audio +} // namespace kiwano + +#endif diff --git a/src/kiwano-audio/Sound.cpp b/src/kiwano-audio/Sound.cpp index 3be889fe..8bbbdebf 100644 --- a/src/kiwano-audio/Sound.cpp +++ b/src/kiwano-audio/Sound.cpp @@ -1,418 +1,407 @@ -// Copyright (c) 2016-2018 Kiwano - Nomango -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#include -#include -#include - -namespace kiwano -{ -namespace audio -{ - -Sound::Sound(const String& file_path) - : Sound() -{ - TranscoderPtr transcoder = MakePtr(file_path); - Load(transcoder); -} - -Sound::Sound(const Resource& res) - : Sound() -{ - TranscoderPtr transcoder = MakePtr(res); - Load(transcoder); -} - -Sound::Sound(const BinaryData& data, const AudioMetadata& metadata) -{ - TranscoderPtr transcoder = MakePtr(data, metadata); - Load(transcoder); -} - -Sound::Sound(TranscoderPtr transcoder) - : Sound() -{ - Load(transcoder); -} - -Sound::Sound() - : opened_(false) - , playing_(false) - , volume_(1.f) -{ -} - -Sound::~Sound() -{ - Close(); -} - -bool Sound::Load(TranscoderPtr transcoder) -{ - if (!transcoder->IsValid()) - { - return false; +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#include +#include +#include + +namespace kiwano +{ +namespace audio +{ + +Sound::Sound(const String& file_path) + : Sound() +{ + Load(AudioModule::GetInstance().Decode(file_path)); +} + +Sound::Sound(const Resource& res, const String& ext) + : Sound() +{ + Load(AudioModule::GetInstance().Decode(res, ext)); +} + +Sound::Sound(AudioDataPtr data) + : Sound() +{ + Load(data); +} + +Sound::Sound() + : opened_(false) + , playing_(false) + , volume_(1.f) +{ +} + +Sound::~Sound() +{ + Close(); +} + +bool Sound::Load(AudioDataPtr data) +{ + if (!data) + { + return false; } - - BinaryData data_; - AudioMetadata metadata_; - - if (opened_) - { - Close(); - } - if (!AudioModule::GetInstance().CreateSound(*this, transcoder->GetMetadata())) - { - return false; - } - - // reset volume - ResetVolume(); - - coder_ = transcoder; - opened_ = true; - return true; -} - -void Sound::Play(int loop_count) -{ - if (!opened_) - { - KGE_ERRORF("Sound must be opened first!"); - return; - } - - auto voice = GetNativePtr(); - KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); - - // if sound stream is not empty, stop() will clear it - XAUDIO2_VOICE_STATE 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 data = coder_->GetData(); - - XAUDIO2_BUFFER xaudio2_buffer = { 0 }; - xaudio2_buffer.pAudioData = reinterpret_cast(data.buffer); - xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM; - xaudio2_buffer.AudioBytes = UINT32(data.size); - xaudio2_buffer.LoopCount = static_cast(loop_count); - - HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer); - if (SUCCEEDED(hr)) - { - hr = voice->Start(); - } - - if (FAILED(hr)) - { - KGE_ERRORF("Submitting source buffer failed with HRESULT of %08X", hr); - } - - playing_ = SUCCEEDED(hr); -} - -void Sound::Pause() -{ - auto voice = GetNativePtr(); - KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); - - 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() -{ - auto voice = GetNativePtr(); - KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); - - 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() -{ - auto voice = GetNativePtr(); - KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); - - HRESULT hr = voice->Stop(); - - if (SUCCEEDED(hr)) - hr = voice->ExitLoop(); - - if (SUCCEEDED(hr)) - hr = voice->FlushSourceBuffers(); - - if (SUCCEEDED(hr)) - playing_ = false; - - if (FAILED(hr)) - { - KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr); - } -} - -void Sound::Close() -{ - auto voice = GetNativePtr(); - if (voice) - { - voice->Stop(); - voice->FlushSourceBuffers(); - voice->DestroyVoice(); - } - - coder_ = nullptr; - opened_ = false; - playing_ = false; -} - -bool Sound::IsPlaying() const -{ - if (opened_) - { - if (!playing_) - return false; - - auto voice = GetNativePtr(); - if (!voice) - return false; - - XAUDIO2_VOICE_STATE state; - voice->GetState(&state); - return !!state.BuffersQueued; - } - return false; -} - -float Sound::GetVolume() const -{ - return volume_; -} - -void Sound::SetVolume(float volume) -{ - if (volume_ == volume) - { - return; - } - volume_ = volume; - - auto voice = GetNativePtr(); - if (voice) - { - float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_); - actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL); - voice->SetVolume(actual_volume); - } -} - -void Sound::ResetVolume() -{ - const float old_volume = volume_; - - volume_ += 1.f; - SetVolume(old_volume); -} - -SoundCallbackPtr Sound::GetCallbackChain() -{ - class SoundCallbackChain : public SoundCallback - { - public: - Sound* sound = nullptr; - - void OnStart(Sound*) override - { - for (auto& cb : sound->GetCallbacks()) - { - if (cb) - { - cb->OnStart(sound); - } - } - RemoveUsedCallbacks(); - } - - void OnLoopEnd(Sound*) override - { - for (auto& cb : sound->GetCallbacks()) - { - if (cb) - { - cb->OnLoopEnd(sound); - } - } - RemoveUsedCallbacks(); - } - - void OnEnd(Sound*) override - { - for (auto& cb : sound->GetCallbacks()) - { - if (cb) - { - cb->OnEnd(sound); - } - } - RemoveUsedCallbacks(); - } - - 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; - } - - void RemoveUsedCallbacks() - { - auto& cbs = sound->GetCallbacks(); - auto iter = cbs.begin(); - while (iter != cbs.end()) - { - if (*iter == nullptr) - { - iter = cbs.erase(iter); - } - else - { - iter++; - } - } - } - }; - - if (!callback_chain_) - { - auto chain = MakePtr(); - chain->sound = this; - callback_chain_ = chain; - } - return callback_chain_; -} - -SoundCallbackPtr SoundCallback::OnStart(const Function& cb) -{ - class SoundCallbackFunc : public SoundCallback - { - public: - Function cb; - - void OnStart(Sound* sound) override - { - if (cb) - { - cb(sound); - } - } - }; - auto ptr = MakePtr(); - ptr->cb = cb; - return ptr; -} - -SoundCallbackPtr SoundCallback::OnLoopEnd(const Function& cb) -{ - class SoundCallbackFunc : public SoundCallback - { - public: - Function cb; - - void OnLoopEnd(Sound* sound) override - { - if (cb) - { - cb(sound); - } - } - }; - auto ptr = MakePtr(); - ptr->cb = cb; - return ptr; -} - -SoundCallbackPtr SoundCallback::OnEnd(const Function& cb) -{ - class SoundCallbackFunc : public SoundCallback - { - public: - Function cb; - - void OnEnd(Sound* sound) override - { - if (cb) - { - cb(sound); - } - } - }; - auto ptr = MakePtr(); - ptr->cb = cb; - return ptr; -} - -SoundCallbackPtr SoundCallback::OnVolumeChanged(const Function& cb) -{ - class SoundCallbackFunc : public SoundCallback - { - public: - Function cb; - - float OnVolumeChanged(Sound* sound, float volume) override - { - if (cb) - { - return cb(sound, volume); - } - return volume; - } - }; - auto ptr = MakePtr(); - ptr->cb = cb; - return ptr; -} - -} // namespace audio -} // namespace kiwano + if (opened_) + { + Close(); + } + if (!AudioModule::GetInstance().CreateSound(*this, data)) + { + return false; + } + + // reset volume + ResetVolume(); + + data_ = data; + opened_ = true; + return true; +} + +void Sound::Play(int loop_count) +{ + if (!opened_) + { + KGE_ERRORF("Sound must be opened first!"); + return; + } + + auto voice = GetNative(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); + + // if sound stream is not empty, stop() will clear it + XAUDIO2_VOICE_STATE 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 data = data_->GetData(); + + XAUDIO2_BUFFER xaudio2_buffer = { 0 }; + xaudio2_buffer.pAudioData = reinterpret_cast(data.buffer); + xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM; + xaudio2_buffer.AudioBytes = UINT32(data.size); + xaudio2_buffer.LoopCount = static_cast(loop_count); + + HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer); + if (SUCCEEDED(hr)) + { + hr = voice->Start(); + } + + if (FAILED(hr)) + { + KGE_ERRORF("Submitting source buffer failed with HRESULT of %08X", hr); + } + + playing_ = SUCCEEDED(hr); +} + +void Sound::Pause() +{ + auto voice = GetNative(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); + + 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() +{ + auto voice = GetNative(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); + + 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() +{ + auto voice = GetNative(); + KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); + + HRESULT hr = voice->Stop(); + + if (SUCCEEDED(hr)) + hr = voice->ExitLoop(); + + if (SUCCEEDED(hr)) + hr = voice->FlushSourceBuffers(); + + if (SUCCEEDED(hr)) + playing_ = false; + + if (FAILED(hr)) + { + KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr); + } +} + +void Sound::Close() +{ + auto voice = GetNative(); + if (voice) + { + voice->Stop(); + voice->FlushSourceBuffers(); + voice->DestroyVoice(); + } + + data_ = nullptr; + opened_ = false; + playing_ = false; +} + +bool Sound::IsPlaying() const +{ + if (opened_) + { + if (!playing_) + return false; + + auto voice = GetNative(); + if (!voice) + return false; + + XAUDIO2_VOICE_STATE state; + voice->GetState(&state); + return !!state.BuffersQueued; + } + return false; +} + +float Sound::GetVolume() const +{ + return volume_; +} + +void Sound::SetVolume(float volume) +{ + if (volume_ == volume) + { + return; + } + volume_ = volume; + + auto voice = GetNative(); + if (voice) + { + float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_); + actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL); + voice->SetVolume(actual_volume); + } +} + +void Sound::ResetVolume() +{ + const float old_volume = volume_; + + volume_ += 1.f; + SetVolume(old_volume); +} + +SoundCallbackPtr Sound::GetCallbackChain() +{ + class SoundCallbackChain : public SoundCallback + { + public: + Sound* sound = nullptr; + + void OnStart(Sound*) override + { + for (auto& cb : sound->GetCallbacks()) + { + if (cb) + { + cb->OnStart(sound); + } + } + RemoveUsedCallbacks(); + } + + void OnLoopEnd(Sound*) override + { + for (auto& cb : sound->GetCallbacks()) + { + if (cb) + { + cb->OnLoopEnd(sound); + } + } + RemoveUsedCallbacks(); + } + + void OnEnd(Sound*) override + { + for (auto& cb : sound->GetCallbacks()) + { + if (cb) + { + cb->OnEnd(sound); + } + } + RemoveUsedCallbacks(); + } + + 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; + } + + void RemoveUsedCallbacks() + { + auto& cbs = sound->GetCallbacks(); + auto iter = cbs.begin(); + while (iter != cbs.end()) + { + if (*iter == nullptr) + { + iter = cbs.erase(iter); + } + else + { + iter++; + } + } + } + }; + + if (!callback_chain_) + { + auto chain = MakePtr(); + chain->sound = this; + callback_chain_ = chain; + } + return callback_chain_; +} + +SoundCallbackPtr SoundCallback::OnStart(const Function& cb) +{ + class SoundCallbackFunc : public SoundCallback + { + public: + Function cb; + + void OnStart(Sound* sound) override + { + if (cb) + { + cb(sound); + } + } + }; + auto ptr = MakePtr(); + ptr->cb = cb; + return ptr; +} + +SoundCallbackPtr SoundCallback::OnLoopEnd(const Function& cb) +{ + class SoundCallbackFunc : public SoundCallback + { + public: + Function cb; + + void OnLoopEnd(Sound* sound) override + { + if (cb) + { + cb(sound); + } + } + }; + auto ptr = MakePtr(); + ptr->cb = cb; + return ptr; +} + +SoundCallbackPtr SoundCallback::OnEnd(const Function& cb) +{ + class SoundCallbackFunc : public SoundCallback + { + public: + Function cb; + + void OnEnd(Sound* sound) override + { + if (cb) + { + cb(sound); + } + } + }; + auto ptr = MakePtr(); + ptr->cb = cb; + return ptr; +} + +SoundCallbackPtr SoundCallback::OnVolumeChanged(const Function& cb) +{ + class SoundCallbackFunc : public SoundCallback + { + public: + Function cb; + + float OnVolumeChanged(Sound* sound, float volume) override + { + if (cb) + { + return cb(sound, volume); + } + return volume; + } + }; + auto ptr = MakePtr(); + ptr->cb = cb; + return ptr; +} + +} // namespace audio +} // namespace kiwano diff --git a/src/kiwano-audio/Sound.h b/src/kiwano-audio/Sound.h index ba60a91b..8f17e315 100644 --- a/src/kiwano-audio/Sound.h +++ b/src/kiwano-audio/Sound.h @@ -1,204 +1,194 @@ -// Copyright (c) 2016-2018 Kiwano - Nomango -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#pragma once -#include -#include -#include -#include - -namespace kiwano -{ -namespace audio -{ -class AudioModule; -class SoundPlayer; - -KGE_DECLARE_SMART_PTR(Sound); -KGE_DECLARE_SMART_PTR(SoundCallback); - -/** - * \addtogroup Audio - * @{ - */ - -/** - * \~chinese - * @brief 音频回调 - */ -class KGE_API SoundCallback : public NativeObject -{ -public: - /// \~chinese - /// @brief 创建一个回调,在音频开始播放时执行 - static SoundCallbackPtr OnStart(const Function& cb); - - /// \~chinese - /// @brief 创建一个回调,在音频循环结束时执行 - static SoundCallbackPtr OnLoopEnd(const Function& cb); - - /// \~chinese - /// @brief 创建一个回调,在音频结束时执行 - static SoundCallbackPtr OnEnd(const Function& cb); - - /// \~chinese - /// @brief 创建一个回调,在音频修改音量时执行 - static SoundCallbackPtr OnVolumeChanged(const Function& 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 创建音频对象 - /// @param res 本地音频文件路径 - Sound(const String& file_path); - - /// \~chinese - /// @brief 创建音频对象 - /// @param res 音频资源 - Sound(const Resource& res); +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. - /// \~chinese - /// @brief 创建音频对象 - /// @param data 音频数据 - /// @param metadata 音频元数据 - Sound(const BinaryData& data, const AudioMetadata& metadata); - - /// \~chinese - /// @brief 创建音频对象 - /// @param transcoder 音频解码器 - Sound(TranscoderPtr transcoder); - - Sound(); - - virtual ~Sound(); - - /// \~chinese - /// @brief 播放 - /// @param loop_count 播放循环次数,设置 -1 为循环播放 - void Play(int loop_count = 0); - - /// \~chinese - /// @brief 暂停 - void Pause(); - - /// \~chinese - /// @brief 继续 - void Resume(); - - /// \~chinese - /// @brief 停止 - void Stop(); - - /// \~chinese - /// @brief 关闭并销毁资源 - void Close(); - - /// \~chinese - /// @brief 是否正在播放 - bool IsPlaying() const; - - /// \~chinese - /// @brief 获取音量 - float GetVolume() const; - - /// \~chinese - /// @brief 设置音量 - /// @param volume 音量大小,1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量 - void SetVolume(float volume); - - /// \~chinese - /// @brief 添加回调 - void AddCallback(SoundCallbackPtr callback); - - /// \~chinese - /// @brief 获取所有回调 - List& GetCallbacks(); - - /// \~chinese - /// @brief 获取所有回调 - const List& GetCallbacks() const; - -protected: - /// \~chinese - /// @brief 加载音频 - /// @param transcoder 音频解码器 - bool Load(TranscoderPtr transcoder); - - SoundCallbackPtr GetCallbackChain(); - - void ResetVolume(); - -private: - bool opened_; - bool playing_; - float volume_; - TranscoderPtr coder_; - - SoundCallbackPtr callback_chain_; - List callbacks_; -}; - -/** @} */ - -inline List& kiwano::audio::Sound::GetCallbacks() -{ - return callbacks_; -} - -inline const List& kiwano::audio::Sound::GetCallbacks() const -{ - return callbacks_; -} - -inline void kiwano::audio::Sound::AddCallback(SoundCallbackPtr callback) -{ - callbacks_.push_back(callback); -} - -} // namespace audio -} // namespace kiwano +#pragma once +#include +#include + +namespace kiwano +{ +namespace audio +{ +class AudioModule; +class SoundPlayer; + +KGE_DECLARE_SMART_PTR(Sound); +KGE_DECLARE_SMART_PTR(SoundCallback); + +/** + * \addtogroup Audio + * @{ + */ + +/** + * \~chinese + * @brief 音频回调 + */ +class KGE_API SoundCallback : public NativeObject +{ +public: + /// \~chinese + /// @brief 创建一个回调,在音频开始播放时执行 + static SoundCallbackPtr OnStart(const Function& cb); + + /// \~chinese + /// @brief 创建一个回调,在音频循环结束时执行 + static SoundCallbackPtr OnLoopEnd(const Function& cb); + + /// \~chinese + /// @brief 创建一个回调,在音频结束时执行 + static SoundCallbackPtr OnEnd(const Function& cb); + + /// \~chinese + /// @brief 创建一个回调,在音频修改音量时执行 + static SoundCallbackPtr OnVolumeChanged(const Function& 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 创建音频对象 + /// @param file_path 本地音频文件路径 + Sound(const String& file_path); + + /// \~chinese + /// @brief 创建音频对象 + /// @param res 音频资源 + /// @param ext 音频类型,决定了使用何种解码器 + Sound(const Resource& res, const String& ext = ""); + + /// \~chinese + /// @brief 创建音频对象 + /// @param data 音频数据 + Sound(AudioDataPtr data); + + Sound(); + + virtual ~Sound(); + + /// \~chinese + /// @brief 播放 + /// @param loop_count 播放循环次数,设置 -1 为循环播放 + void Play(int loop_count = 0); + + /// \~chinese + /// @brief 暂停 + void Pause(); + + /// \~chinese + /// @brief 继续 + void Resume(); + + /// \~chinese + /// @brief 停止 + void Stop(); + + /// \~chinese + /// @brief 关闭并销毁资源 + void Close(); + + /// \~chinese + /// @brief 是否正在播放 + bool IsPlaying() const; + + /// \~chinese + /// @brief 获取音量 + float GetVolume() const; + + /// \~chinese + /// @brief 设置音量 + /// @param volume 音量大小,1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量 + void SetVolume(float volume); + + /// \~chinese + /// @brief 添加回调 + void AddCallback(SoundCallbackPtr callback); + + /// \~chinese + /// @brief 获取所有回调 + List& GetCallbacks(); + + /// \~chinese + /// @brief 获取所有回调 + const List& GetCallbacks() const; + +protected: + bool Load(AudioDataPtr data); + + SoundCallbackPtr GetCallbackChain(); + + void ResetVolume(); + +private: + bool opened_; + bool playing_; + float volume_; + AudioDataPtr data_; + + SoundCallbackPtr callback_chain_; + List callbacks_; +}; + +/** @} */ + +inline List& kiwano::audio::Sound::GetCallbacks() +{ + return callbacks_; +} + +inline const List& kiwano::audio::Sound::GetCallbacks() const +{ + return callbacks_; +} + +inline void kiwano::audio::Sound::AddCallback(SoundCallbackPtr callback) +{ + callbacks_.push_back(callback); +} + +} // namespace audio +} // namespace kiwano diff --git a/src/kiwano-audio/SoundPlayer.cpp b/src/kiwano-audio/SoundPlayer.cpp index aac8dce7..ab4883da 100644 --- a/src/kiwano-audio/SoundPlayer.cpp +++ b/src/kiwano-audio/SoundPlayer.cpp @@ -56,14 +56,14 @@ SoundPlayer::SoundPlayer() SoundPlayer::~SoundPlayer() {} -TranscoderPtr SoundPlayer::Preload(const String& file_path) +AudioDataPtr SoundPlayer::Preload(const String& file_path) { size_t hash_code = std::hash{}(file_path); if (cache_.count(hash_code)) { return cache_.at(hash_code); } - TranscoderPtr ptr = MakePtr(file_path); + AudioDataPtr ptr = AudioModule::GetInstance().Decode(file_path); if (ptr) { cache_.insert(std::make_pair(hash_code, ptr)); @@ -71,14 +71,14 @@ TranscoderPtr SoundPlayer::Preload(const String& file_path) return ptr; } -TranscoderPtr SoundPlayer::Preload(const Resource& res) +AudioDataPtr SoundPlayer::Preload(const Resource& res, const String& ext) { size_t hash_code = res.GetId(); if (cache_.count(hash_code)) { return cache_.at(hash_code); } - TranscoderPtr ptr = MakePtr(res); + AudioDataPtr ptr = AudioModule::GetInstance().Decode(res, ext); if (ptr) { cache_.insert(std::make_pair(hash_code, ptr)); @@ -96,28 +96,16 @@ void SoundPlayer::Play(SoundPtr sound, int loop_count) } } -SoundPtr SoundPlayer::Play(const String& file_path, int loop_count, std::initializer_list callbacks) +SoundPtr SoundPlayer::Play(const String& file_path, int loop_count) { - TranscoderPtr transcoder = Preload(file_path); - - SoundPtr sound = new Sound(transcoder); - for (const auto& cb : callbacks) - { - sound->AddCallback(cb); - } + SoundPtr sound = new Sound(Preload(file_path)); Play(sound, loop_count); return sound; } -SoundPtr SoundPlayer::Play(const Resource& res, int loop_count, std::initializer_list callbacks) +SoundPtr SoundPlayer::Play(const Resource& res, int loop_count) { - TranscoderPtr transcoder = Preload(res); - - SoundPtr sound = new Sound(transcoder); - for (const auto& cb : callbacks) - { - sound->AddCallback(cb); - } + SoundPtr sound = new Sound(Preload(res)); Play(sound, loop_count); return sound; } diff --git a/src/kiwano-audio/SoundPlayer.h b/src/kiwano-audio/SoundPlayer.h index bc8ee10e..d5a9c204 100644 --- a/src/kiwano-audio/SoundPlayer.h +++ b/src/kiwano-audio/SoundPlayer.h @@ -48,11 +48,11 @@ public: /// \~chinese /// @brief 预加载音频 /// @details - TranscoderPtr Preload(const String& file_path); + AudioDataPtr Preload(const String& file_path); /// \~chinese /// @brief 预加载音频资源 - TranscoderPtr Preload(const Resource& res); + AudioDataPtr Preload(const Resource& res, const String& ext = ""); /// \~chinese /// @brief 播放音频 @@ -64,15 +64,13 @@ public: /// @brief 播放音频 /// @param file_path 本地音频文件路径 /// @param loop_count 播放循环次数,设置 -1 为循环播放 - /// @param callbacks 注册回调 - SoundPtr Play(const String& file_path, int loop_count = 0, std::initializer_list callbacks = {}); + SoundPtr Play(const String& file_path, int loop_count = 0); /// \~chinese /// @brief 播放音频 /// @param res 音频资源 /// @param loop_count 播放循环次数,设置 -1 为循环播放 - /// @param callbacks 注册回调 - SoundPtr Play(const Resource& res, int loop_count = 0, std::initializer_list callbacks = {}); + SoundPtr Play(const Resource& res, int loop_count = 0); /// \~chinese /// @brief 暂停所有音频 @@ -120,7 +118,7 @@ protected: SoundList trash_; SoundCallbackPtr callback_; - UnorderedMap cache_; + UnorderedMap cache_; }; /** @} */ diff --git a/src/kiwano-audio/Transcoder.h b/src/kiwano-audio/Transcoder.h index 9909c7ba..c0df6c13 100644 --- a/src/kiwano-audio/Transcoder.h +++ b/src/kiwano-audio/Transcoder.h @@ -1,110 +1,56 @@ -// Copyright (c) 2016-2018 Kiwano - Nomango -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#pragma once -#include -#include - -namespace kiwano -{ -namespace audio -{ -class AudioModule; - -KGE_DECLARE_SMART_PTR(Transcoder); - -/** - * \addtogroup Audio - * @{ +// Copyright (c) 2016-2018 Kiwano - Nomango +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once +#include +#include + +namespace kiwano +{ +namespace audio +{ + +KGE_DECLARE_SMART_PTR(Transcoder); + +/** + * \addtogroup Audio + * @{ */ -/** - * \~chinese - * @brief 音频格式 - */ -enum class AudioFormat +/** + * \~chinese + * @brief 音频解码器 + */ +class KGE_API Transcoder : public ObjectBase { - PCM, +public: + Transcoder() = default; + + virtual ~Transcoder() = default; + + virtual AudioDataPtr Decode(const String& file_path) = 0; + + virtual AudioDataPtr Decode(const Resource& res) = 0; }; -/** - * \~chinese - * @brief 音频元数据 - */ -struct AudioMetadata -{ - AudioFormat format = AudioFormat::PCM; ///< 音频格式 - uint16_t channels = 2; ///< 声道,单声道为1,立体声为2 - uint32_t samples_per_sec = 44100; ///< 采样率,11025 表示 11.025kHz。PCM格式的采样率通常为44.1kHz - uint16_t bits_per_sample = 16; ///< 位深,PCM格式为 8 或 16 - uint16_t block_align = 4; ///< 块对齐,PCM格式通常是 (channels * bits_per_sample) / 8 - void* extra_data = nullptr; ///< 额外数据,不要设置这个字段 -}; - -/** - * \~chinese - * @brief 音频解码器 - */ -class KGE_API Transcoder : public ObjectBase -{ - friend class AudioModule; - -public: - Transcoder(const String& file_path); +/** @} */ - Transcoder(const Resource& res); - - Transcoder(const BinaryData& data, const AudioMetadata& metadata); - - virtual ~Transcoder(); - - /// \~chinese - /// @brief 获取音频元数据 - AudioMetadata GetMetadata() const; - - /// \~chinese - /// @brief 获取数据 - BinaryData GetData() const; - - /// \~chinese - /// @brief 清空数据 - void Clear(); - -private: - /// \~chinese - /// @brief 解码本地音频文件 - bool Load(const String& file_path); - - /// \~chinese - /// @brief 解码音频资源 - bool Load(const Resource& res); - - bool Load(const BinaryData& data, const AudioMetadata& metadata); - -private: - std::unique_ptr raw_; - BinaryData data_; - AudioMetadata metadata_; -}; - -/** @} */ - -} // namespace audio -} // namespace kiwano +} // namespace audio +} // namespace kiwano diff --git a/src/kiwano-audio/libraries.cpp b/src/kiwano-audio/libraries.cpp index dc954079..52998d80 100644 --- a/src/kiwano-audio/libraries.cpp +++ b/src/kiwano-audio/libraries.cpp @@ -55,45 +55,6 @@ XAudio2::XAudio2() KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load xaudio2.dll failed"); } } - -MediaFoundation::MediaFoundation() - : mfplat() - , mfreadwrite() - , MFStartup(nullptr) - , MFShutdown(nullptr) - , MFCreateMediaType(nullptr) - , MFCreateWaveFormatExFromMFMediaType(nullptr) - , MFCreateSourceReaderFromURL(nullptr) - , MFCreateSourceReaderFromByteStream(nullptr) - , MFCreateMFByteStreamOnStream(nullptr) -{ - if (mfplat.Load("Mfplat.dll")) - { - MFStartup = mfplat.GetProcess("MFStartup"); - MFShutdown = mfplat.GetProcess("MFShutdown"); - MFCreateMediaType = mfplat.GetProcess("MFCreateMediaType"); - MFCreateWaveFormatExFromMFMediaType = - mfplat.GetProcess("MFCreateWaveFormatExFromMFMediaType"); - MFCreateMFByteStreamOnStream = - mfplat.GetProcess("MFCreateMFByteStreamOnStream"); - } - else - { - KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfplat.dll failed"); - } - - if (mfreadwrite.Load("Mfreadwrite.dll")) - { - MFCreateSourceReaderFromURL = - mfreadwrite.GetProcess("MFCreateSourceReaderFromURL"); - MFCreateSourceReaderFromByteStream = - mfreadwrite.GetProcess("MFCreateSourceReaderFromByteStream"); - } - else - { - KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfreadwrite.dll failed"); - } -} } // namespace dlls } // namespace audio } // namespace kiwano diff --git a/src/kiwano-audio/libraries.h b/src/kiwano-audio/libraries.h index 35324db0..316bd067 100644 --- a/src/kiwano-audio/libraries.h +++ b/src/kiwano-audio/libraries.h @@ -20,9 +20,6 @@ #pragma once #include -#include -#include -#include #include #ifndef KGE_DOXYGEN_DO_NOT_INCLUDE @@ -55,42 +52,6 @@ private: Library xaudio2; }; - -class KGE_API MediaFoundation -{ -public: - static inline MediaFoundation& Get() - { - static MediaFoundation instance; - return instance; - } - - // MediaFoundation functions - typedef HRESULT(WINAPI* PFN_MFStartup)(ULONG, DWORD); - typedef HRESULT(WINAPI* PFN_MFShutdown)(); - typedef HRESULT(WINAPI* PFN_MFCreateMediaType)(IMFMediaType**); - typedef HRESULT(WINAPI* PFN_MFCreateWaveFormatExFromMFMediaType)(IMFMediaType*, WAVEFORMATEX**, UINT32*, UINT32); - typedef HRESULT(WINAPI* PFN_MFCreateSourceReaderFromURL)(LPCWSTR, IMFAttributes*, IMFSourceReader**); - typedef HRESULT(WINAPI* PFN_MFCreateSourceReaderFromByteStream)(IMFByteStream*, IMFAttributes*, IMFSourceReader**); - typedef HRESULT(WINAPI* PFN_MFCreateMFByteStreamOnStream)(IStream*, IMFByteStream**); - - PFN_MFStartup MFStartup; - PFN_MFShutdown MFShutdown; - PFN_MFCreateMediaType MFCreateMediaType; - PFN_MFCreateWaveFormatExFromMFMediaType MFCreateWaveFormatExFromMFMediaType; - PFN_MFCreateSourceReaderFromURL MFCreateSourceReaderFromURL; - PFN_MFCreateSourceReaderFromByteStream MFCreateSourceReaderFromByteStream; - PFN_MFCreateMFByteStreamOnStream MFCreateMFByteStreamOnStream; - -private: - MediaFoundation(); - - MediaFoundation(const MediaFoundation&) = delete; - MediaFoundation& operator=(const MediaFoundation&) = delete; - - Library mfplat; - Library mfreadwrite; -}; } // namespace dlls } // namespace audio } // namespace kiwano diff --git a/src/kiwano/platform/FileSystem.cpp b/src/kiwano/platform/FileSystem.cpp index 6ff5239c..3da8277c 100644 --- a/src/kiwano/platform/FileSystem.cpp +++ b/src/kiwano/platform/FileSystem.cpp @@ -130,6 +130,16 @@ String FileSystem::GetFullPathForFile(const String& file) const return ""; } +String FileSystem::GetFileExt(const String& file) const +{ + const auto pos = file.find_last_of("."); + if (pos == String::npos) + { + return ""; + } + return file.substr(pos + 1); +} + void FileSystem::AddFileLookupRule(const String& key, const String& file_path) { file_lookup_dict_.emplace(key, ConvertPathFormat(file_path)); diff --git a/src/kiwano/platform/FileSystem.h b/src/kiwano/platform/FileSystem.h index a012ff78..794e7ca0 100644 --- a/src/kiwano/platform/FileSystem.h +++ b/src/kiwano/platform/FileSystem.h @@ -54,6 +54,14 @@ public: */ String GetFullPathForFile(const String& file) const; + /** + * \~chinese + * @brief 获取文件拓展名 + * @param file 文件路径 + * @return 文件拓展名,不包含 . 符号 + */ + String GetFileExt(const String& file) const; + /** * \~chinese * @brief 添加文件路径查找字典规则 diff --git a/src/kiwano/platform/NativeObject.hpp b/src/kiwano/platform/NativeObject.hpp index 6f3d77ec..0b382739 100644 --- a/src/kiwano/platform/NativeObject.hpp +++ b/src/kiwano/platform/NativeObject.hpp @@ -40,9 +40,6 @@ public: template _Ty GetNative() const; - template - _Ty* GetNativePtr() const; - void SetNative(const Any& native); void ResetNative(); @@ -65,17 +62,7 @@ inline _Ty NativeObject::GetNative() const { return native_.Cast<_Ty>(); } - return nullptr; -} - -template -inline _Ty* NativeObject::GetNativePtr() const -{ - if (native_.HasValue()) - { - return native_.Cast<_Ty*>(); - } - return nullptr; + return _Ty{}; } inline void NativeObject::SetNative(const Any& native)