feat: support register custom audio transcoder

This commit is contained in:
Nomango 2023-10-06 18:07:07 +08:00
parent 7a1c565a92
commit 641f78b41e
20 changed files with 1211 additions and 990 deletions

View File

@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\src\kiwano-audio\libraries.h" /> <ClInclude Include="..\..\src\kiwano-audio\AudioData.h" />
<ClInclude Include="..\..\src\kiwano-audio\AudioModule.h" /> <ClInclude Include="..\..\src\kiwano-audio\AudioModule.h" />
<ClInclude Include="..\..\src\kiwano-audio\kiwano-audio.h" /> <ClInclude Include="..\..\src\kiwano-audio\kiwano-audio.h" />
<ClInclude Include="..\..\src\kiwano-audio\libraries.h" />
<ClInclude Include="..\..\src\kiwano-audio\MediaFoundation\mflib.h" />
<ClInclude Include="..\..\src\kiwano-audio\MediaFoundation\Transcoder.h" />
<ClInclude Include="..\..\src\kiwano-audio\Sound.h" /> <ClInclude Include="..\..\src\kiwano-audio\Sound.h" />
<ClInclude Include="..\..\src\kiwano-audio\SoundPlayer.h" /> <ClInclude Include="..\..\src\kiwano-audio\SoundPlayer.h" />
<ClInclude Include="..\..\src\kiwano-audio\Transcoder.h" /> <ClInclude Include="..\..\src\kiwano-audio\Transcoder.h" />
@ -27,11 +30,13 @@
</ProjectConfiguration> </ProjectConfiguration>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\..\src\kiwano-audio\libraries.cpp" /> <ClCompile Include="..\..\src\kiwano-audio\AudioData.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\AudioModule.cpp" /> <ClCompile Include="..\..\src\kiwano-audio\AudioModule.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\libraries.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\MediaFoundation\mflib.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\MediaFoundation\Transcoder.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\Sound.cpp" /> <ClCompile Include="..\..\src\kiwano-audio\Sound.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\SoundPlayer.cpp" /> <ClCompile Include="..\..\src\kiwano-audio\SoundPlayer.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\Transcoder.cpp" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>{1B97937D-8184-426C-BE71-29A163DC76C9}</ProjectGuid> <ProjectGuid>{1B97937D-8184-426C-BE71-29A163DC76C9}</ProjectGuid>
@ -184,4 +189,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>
</Project> </Project>

View File

@ -5,14 +5,32 @@
<ClInclude Include="..\..\src\kiwano-audio\Sound.h" /> <ClInclude Include="..\..\src\kiwano-audio\Sound.h" />
<ClInclude Include="..\..\src\kiwano-audio\SoundPlayer.h" /> <ClInclude Include="..\..\src\kiwano-audio\SoundPlayer.h" />
<ClInclude Include="..\..\src\kiwano-audio\Transcoder.h" /> <ClInclude Include="..\..\src\kiwano-audio\Transcoder.h" />
<ClInclude Include="..\..\src\kiwano-audio\libraries.h" />
<ClInclude Include="..\..\src\kiwano-audio\AudioModule.h" /> <ClInclude Include="..\..\src\kiwano-audio\AudioModule.h" />
<ClInclude Include="..\..\src\kiwano-audio\AudioData.h" />
<ClInclude Include="..\..\src\kiwano-audio\MediaFoundation\Transcoder.h">
<Filter>MediaFoundation</Filter>
</ClInclude>
<ClInclude Include="..\..\src\kiwano-audio\libraries.h" />
<ClInclude Include="..\..\src\kiwano-audio\MediaFoundation\mflib.h">
<Filter>MediaFoundation</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\..\src\kiwano-audio\Sound.cpp" /> <ClCompile Include="..\..\src\kiwano-audio\Sound.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\SoundPlayer.cpp" /> <ClCompile Include="..\..\src\kiwano-audio\SoundPlayer.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\Transcoder.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\libraries.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\AudioModule.cpp" /> <ClCompile Include="..\..\src\kiwano-audio\AudioModule.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\AudioData.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\MediaFoundation\Transcoder.cpp">
<Filter>MediaFoundation</Filter>
</ClCompile>
<ClCompile Include="..\..\src\kiwano-audio\libraries.cpp" />
<ClCompile Include="..\..\src\kiwano-audio\MediaFoundation\mflib.cpp">
<Filter>MediaFoundation</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="MediaFoundation">
<UniqueIdentifier>{213a58dc-8054-4bc7-bac5-456e889ae038}</UniqueIdentifier>
</Filter>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -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 <kiwano-audio/AudioData.h>
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

View File

@ -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 <kiwano/core/BinaryData.h>
#include <kiwano/base/ObjectBase.h>
#include <kiwano/platform/NativeObject.hpp>
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

View File

@ -17,11 +17,12 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 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 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#include <kiwano-audio/AudioModule.h>
#include <kiwano-audio/libraries.h>
#include <kiwano/core/Exception.h> #include <kiwano/core/Exception.h>
#include <kiwano/utils/Logger.h> #include <kiwano/utils/Logger.h>
#include <kiwano/platform/FileSystem.h>
#include <kiwano-audio/AudioModule.h>
#include <kiwano-audio/libraries.h>
#include <kiwano-audio/MediaFoundation/Transcoder.h>
namespace kiwano namespace kiwano
{ {
@ -89,16 +90,16 @@ void AudioModule::SetupModule()
{ {
KGE_DEBUG_LOGF("Creating audio resources"); KGE_DEBUG_LOGF("Creating audio resources");
HRESULT hr = dlls::MediaFoundation::Get().MFStartup(MF_VERSION, MFSTARTUP_FULL); HRESULT hr = dlls::XAudio2::Get().XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (SUCCEEDED(hr))
{
hr = dlls::XAudio2::Get().XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR);
}
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
hr = x_audio2_->CreateMasteringVoice(&mastering_voice_); hr = x_audio2_->CreateMasteringVoice(&mastering_voice_);
}
if (SUCCEEDED(hr))
{
RegisterTranscoder("*", MakePtr<MFTranscoder>());
} }
KGE_THROW_IF_FAILED(hr, "Create audio resources failed"); KGE_THROW_IF_FAILED(hr, "Create audio resources failed");
@ -119,28 +120,28 @@ void AudioModule::DestroyModule()
x_audio2_->Release(); x_audio2_->Release();
x_audio2_ = nullptr; 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!"); KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!");
HRESULT hr = S_OK; 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);
WAVEFORMATEX* wave_format_ptr = &wave_format; WAVEFORMATEX* wave_fmt = data->GetNative<WAVEFORMATEX*>();
if (metadata.extra_data != nullptr) if (wave_fmt == nullptr)
{ {
wave_format_ptr = reinterpret_cast<WAVEFORMATEX*>(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<WAVEFORMATEX*>(data->GetNative().CastPtr<WAVEFORMATEX>());
} }
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
@ -150,7 +151,7 @@ bool AudioModule::CreateSound(Sound& sound, const AudioMetadata& metadata)
auto callback = const_cast<VoiceCallback*>(chain->GetNative().CastPtr<VoiceCallback>()); auto callback = const_cast<VoiceCallback*>(chain->GetNative().CastPtr<VoiceCallback>());
IXAudio2SourceVoice* voice = nullptr; 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)) if (SUCCEEDED(hr))
{ {
sound.Close(); sound.Close();
@ -179,6 +180,51 @@ void AudioModule::Close()
if (x_audio2_) if (x_audio2_)
x_audio2_->StopEngine(); 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 audio
} // namespace kiwano } // namespace kiwano

View File

@ -59,9 +59,30 @@ public:
/// @brief 关闭音频设备 /// @brief 关闭音频设备
void Close(); 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 /// \~chinese
/// @brief 创建音频 /// @brief 创建音频
bool CreateSound(Sound& sound, const AudioMetadata& metadata); bool CreateSound(Sound& sound, AudioDataPtr data);
public: public:
void SetupModule() override; void SetupModule() override;
@ -76,6 +97,8 @@ private:
private: private:
IXAudio2* x_audio2_; IXAudio2* x_audio2_;
IXAudio2MasteringVoice* mastering_voice_; IXAudio2MasteringVoice* mastering_voice_;
UnorderedMap<String, TranscoderPtr> registered_transcoders_;
}; };
/** @} */ /** @} */

View File

@ -18,113 +18,53 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#ifndef INITGUID #include <kiwano-audio/MediaFoundation/Transcoder.h>
#define INITGUID // MFAudioFormat_PCM, MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE, MFMediaType_Audio #include <kiwano-audio/MediaFoundation/mflib.h>
#endif
#include <kiwano-audio/Transcoder.h>
#include <kiwano-audio/libraries.h>
#include <kiwano/core/Common.h> #include <kiwano/core/Common.h>
#include <kiwano/core/Resource.h>
#include <kiwano/utils/Logger.h> #include <kiwano/utils/Logger.h>
#include <kiwano/platform/win32/ComPtr.hpp> #include <kiwano/platform/win32/ComPtr.hpp>
#include <kiwano/platform/win32/libraries.h> #include <kiwano/platform/win32/libraries.h>
#include <kiwano/platform/FileSystem.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
namespace kiwano namespace kiwano
{ {
namespace audio namespace audio
{
HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std::unique_ptr<uint8_t[]>& 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(); HRESULT hr = dlls::MediaFoundation::Get().MFStartup(MF_VERSION, MFSTARTUP_FULL);
} KGE_THROW_IF_FAILED(hr, "Initialize Media Foundation failed!");
AudioMetadata Transcoder::GetMetadata() const
{
return metadata_;
} }
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) ComPtr<IMFSourceReader> reader;
{
WAVEFORMATEX* wave_format = reinterpret_cast<WAVEFORMATEX*>(metadata_.extra_data);
::CoTaskMemFree(wave_format);
metadata_.extra_data = nullptr;
}
data_ = {};
raw_.reset();
}
bool Transcoder::Load(const BinaryData& data, const AudioMetadata& metadata) WideString path = strings::NarrowToWide(file_path);
{ HRESULT hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromURL(path.c_str(), nullptr, &reader);
data_ = data;
metadata_ = metadata;
return true;
}
bool Transcoder::Load(const String& file_path) AudioDataPtr output;
{
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<IMFSourceReader> reader;
HRESULT hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromURL(path.c_str(), nullptr, &reader);
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
hr = ReadSource(reader.Get(), metadata_, raw_, data_.size); hr = ReadSource(output, reader.Get());
if (SUCCEEDED(hr))
{
data_.buffer = raw_.get();
}
} }
if (FAILED(hr)) if (FAILED(hr))
{ {
Fail(strings::Format("%s failed (%#x): %s", __FUNCTION__, hr, "Load audio failed")); 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; HRESULT hr = S_OK;
@ -136,7 +76,7 @@ bool Transcoder::Load(const Resource& res)
if (!data.IsValid()) if (!data.IsValid())
{ {
Fail("invalid audio data"); Fail("invalid audio data");
return false; return nullptr;
} }
stream = win32::dlls::Shlwapi::Get().SHCreateMemStream(static_cast<const BYTE*>(data.buffer), stream = win32::dlls::Shlwapi::Get().SHCreateMemStream(static_cast<const BYTE*>(data.buffer),
@ -145,7 +85,7 @@ bool Transcoder::Load(const Resource& res)
if (stream == nullptr) if (stream == nullptr)
{ {
Fail("SHCreateMemStream failed"); Fail("SHCreateMemStream failed");
return false; return nullptr;
} }
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
@ -158,26 +98,51 @@ bool Transcoder::Load(const Resource& res)
hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromByteStream(byte_stream.Get(), nullptr, &reader); hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromByteStream(byte_stream.Get(), nullptr, &reader);
} }
AudioDataPtr output;
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
hr = ReadSource(reader.Get(), metadata_, raw_, data_.size); hr = ReadSource(output, reader.Get());
if (SUCCEEDED(hr))
{
data_.buffer = raw_.get();
}
} }
if (FAILED(hr)) if (FAILED(hr))
{ {
Fail(strings::Format("%s failed (%#x): %s", __FUNCTION__, hr, "Load audio failed")); 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<uint8_t[]>& output_data, class MFAudioData : public AudioData
uint32_t& output_size) {
public:
MFAudioData(WAVEFORMATEX* wave_fmt, std::unique_ptr<uint8_t[]> 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<uint8_t[]> raw_;
};
HRESULT ReadSource(AudioDataPtr& output, IMFSourceReader* reader)
{ {
HRESULT hr = S_OK; 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); 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; DWORD max_stream_size = 0;
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
@ -241,9 +196,9 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std:
PROPVARIANT prop; PROPVARIANT prop;
PropVariantInit(&prop); PropVariantInit(&prop);
hr = reader->GetPresentationAttribute((DWORD)MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &prop); hr = reader->GetPresentationAttribute((DWORD)MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &prop);
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
LONGLONG duration = prop.uhVal.QuadPart; LONGLONG duration = prop.uhVal.QuadPart;
max_stream_size = static_cast<DWORD>((duration * wave_format->nAvgBytesPerSec) / 10000000 + 1); max_stream_size = static_cast<DWORD>((duration * wave_format->nAvgBytesPerSec) / 10000000 + 1);
@ -255,9 +210,9 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std:
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
DWORD flags = 0; DWORD flags = 0;
DWORD position = 0; DWORD position = 0;
output_data = std::make_unique<uint8_t[]>(max_stream_size); auto output_data = std::make_unique<uint8_t[]>(max_stream_size);
ComPtr<IMFSample> sample; ComPtr<IMFSample> sample;
ComPtr<IMFMediaBuffer> buffer; ComPtr<IMFMediaBuffer> buffer;
@ -319,12 +274,8 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std:
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
output_size = uint32_t(position); output = new MFAudioData(wave_format, std::move(output_data), uint32_t(position));
} return hr;
else
{
output_data.reset();
output_size = 0;
} }
} }
} }

View File

@ -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 <kiwano-audio/Transcoder.h>
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

View File

@ -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 <kiwano-audio/MediaFoundation/mflib.h>
#include <kiwano/utils/Logger.h>
#include <kiwano/core/Exception.h>
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<PFN_MFStartup>("MFStartup");
MFShutdown = mfplat.GetProcess<PFN_MFShutdown>("MFShutdown");
MFCreateMediaType = mfplat.GetProcess<PFN_MFCreateMediaType>("MFCreateMediaType");
MFCreateWaveFormatExFromMFMediaType =
mfplat.GetProcess<PFN_MFCreateWaveFormatExFromMFMediaType>("MFCreateWaveFormatExFromMFMediaType");
MFCreateMFByteStreamOnStream =
mfplat.GetProcess<PFN_MFCreateMFByteStreamOnStream>("MFCreateMFByteStreamOnStream");
}
else
{
KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfplat.dll failed");
}
if (mfreadwrite.Load("Mfreadwrite.dll"))
{
MFCreateSourceReaderFromURL =
mfreadwrite.GetProcess<PFN_MFCreateSourceReaderFromURL>("MFCreateSourceReaderFromURL");
MFCreateSourceReaderFromByteStream =
mfreadwrite.GetProcess<PFN_MFCreateSourceReaderFromByteStream>("MFCreateSourceReaderFromByteStream");
}
else
{
KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfreadwrite.dll failed");
}
}
} // namespace dlls
} // namespace audio
} // namespace kiwano

View File

@ -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 <kiwano/core/Library.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#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

View File

@ -1,418 +1,407 @@
// Copyright (c) 2016-2018 Kiwano - Nomango // Copyright (c) 2016-2018 Kiwano - Nomango
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights // in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is // copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: // furnished to do so, subject to the following conditions:
// //
// The above copyright notice and this permission notice shall be included in // The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software. // all copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 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 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#include <kiwano-audio/AudioModule.h> #include <kiwano-audio/AudioModule.h>
#include <kiwano-audio/Sound.h> #include <kiwano-audio/Sound.h>
#include <kiwano/utils/Logger.h> #include <kiwano/utils/Logger.h>
#include <xaudio2.h>
namespace kiwano
{ namespace kiwano
namespace audio {
{ namespace audio
{
Sound::Sound(const String& file_path)
: Sound() Sound::Sound(const String& file_path)
{ : Sound()
TranscoderPtr transcoder = MakePtr<Transcoder>(file_path); {
Load(transcoder); Load(AudioModule::GetInstance().Decode(file_path));
} }
Sound::Sound(const Resource& res) Sound::Sound(const Resource& res, const String& ext)
: Sound() : Sound()
{ {
TranscoderPtr transcoder = MakePtr<Transcoder>(res); Load(AudioModule::GetInstance().Decode(res, ext));
Load(transcoder); }
}
Sound::Sound(AudioDataPtr data)
Sound::Sound(const BinaryData& data, const AudioMetadata& metadata) : Sound()
{ {
TranscoderPtr transcoder = MakePtr<Transcoder>(data, metadata); Load(data);
Load(transcoder); }
}
Sound::Sound()
Sound::Sound(TranscoderPtr transcoder) : opened_(false)
: Sound() , playing_(false)
{ , volume_(1.f)
Load(transcoder); {
} }
Sound::Sound() Sound::~Sound()
: opened_(false) {
, playing_(false) Close();
, volume_(1.f) }
{
} bool Sound::Load(AudioDataPtr data)
{
Sound::~Sound() if (!data)
{ {
Close(); return false;
}
bool Sound::Load(TranscoderPtr transcoder)
{
if (!transcoder->IsValid())
{
return false;
} }
if (opened_)
BinaryData data_; {
AudioMetadata metadata_; Close();
}
if (opened_) if (!AudioModule::GetInstance().CreateSound(*this, data))
{ {
Close(); return false;
} }
if (!AudioModule::GetInstance().CreateSound(*this, transcoder->GetMetadata()))
{ // reset volume
return false; ResetVolume();
}
data_ = data;
// reset volume opened_ = true;
ResetVolume(); return true;
}
coder_ = transcoder;
opened_ = true; void Sound::Play(int loop_count)
return true; {
} if (!opened_)
{
void Sound::Play(int loop_count) KGE_ERRORF("Sound must be opened first!");
{ return;
if (!opened_) }
{
KGE_ERRORF("Sound must be opened first!"); auto voice = GetNative<IXAudio2SourceVoice*>();
return; KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
}
// if sound stream is not empty, stop() will clear it
auto voice = GetNativePtr<IXAudio2SourceVoice>(); XAUDIO2_VOICE_STATE state;
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); voice->GetState(&state);
if (state.BuffersQueued)
// if sound stream is not empty, stop() will clear it Stop();
XAUDIO2_VOICE_STATE state;
voice->GetState(&state); // clamp loop count
if (state.BuffersQueued) loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1);
Stop();
auto data = data_->GetData();
// clamp loop count
loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1); XAUDIO2_BUFFER xaudio2_buffer = { 0 };
xaudio2_buffer.pAudioData = reinterpret_cast<BYTE*>(data.buffer);
auto data = coder_->GetData(); xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM;
xaudio2_buffer.AudioBytes = UINT32(data.size);
XAUDIO2_BUFFER xaudio2_buffer = { 0 }; xaudio2_buffer.LoopCount = static_cast<uint32_t>(loop_count);
xaudio2_buffer.pAudioData = reinterpret_cast<BYTE*>(data.buffer);
xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM; HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer);
xaudio2_buffer.AudioBytes = UINT32(data.size); if (SUCCEEDED(hr))
xaudio2_buffer.LoopCount = static_cast<uint32_t>(loop_count); {
hr = voice->Start();
HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer); }
if (SUCCEEDED(hr))
{ if (FAILED(hr))
hr = voice->Start(); {
} KGE_ERRORF("Submitting source buffer failed with HRESULT of %08X", hr);
}
if (FAILED(hr))
{ playing_ = SUCCEEDED(hr);
KGE_ERRORF("Submitting source buffer failed with HRESULT of %08X", hr); }
}
void Sound::Pause()
playing_ = SUCCEEDED(hr); {
} auto voice = GetNative<IXAudio2SourceVoice*>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
void Sound::Pause()
{ HRESULT hr = voice->Stop();
auto voice = GetNativePtr<IXAudio2SourceVoice>(); if (SUCCEEDED(hr))
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); playing_ = false;
HRESULT hr = voice->Stop(); if (FAILED(hr))
if (SUCCEEDED(hr)) {
playing_ = false; KGE_ERRORF("Pause voice failed with HRESULT of %08X", hr);
}
if (FAILED(hr)) }
{
KGE_ERRORF("Pause voice failed with HRESULT of %08X", hr); void Sound::Resume()
} {
} auto voice = GetNative<IXAudio2SourceVoice*>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
void Sound::Resume()
{ HRESULT hr = voice->Start();
auto voice = GetNativePtr<IXAudio2SourceVoice>(); if (SUCCEEDED(hr))
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); playing_ = true;
HRESULT hr = voice->Start(); if (FAILED(hr))
if (SUCCEEDED(hr)) {
playing_ = true; KGE_ERRORF("Start voice failed with HRESULT of %08X", hr);
}
if (FAILED(hr)) }
{
KGE_ERRORF("Start voice failed with HRESULT of %08X", hr); void Sound::Stop()
} {
} auto voice = GetNative<IXAudio2SourceVoice*>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
void Sound::Stop()
{ HRESULT hr = voice->Stop();
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); if (SUCCEEDED(hr))
hr = voice->ExitLoop();
HRESULT hr = voice->Stop();
if (SUCCEEDED(hr))
if (SUCCEEDED(hr)) hr = voice->FlushSourceBuffers();
hr = voice->ExitLoop();
if (SUCCEEDED(hr))
if (SUCCEEDED(hr)) playing_ = false;
hr = voice->FlushSourceBuffers();
if (FAILED(hr))
if (SUCCEEDED(hr)) {
playing_ = false; KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr);
}
if (FAILED(hr)) }
{
KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr); void Sound::Close()
} {
} auto voice = GetNative<IXAudio2SourceVoice*>();
if (voice)
void Sound::Close() {
{ voice->Stop();
auto voice = GetNativePtr<IXAudio2SourceVoice>(); voice->FlushSourceBuffers();
if (voice) voice->DestroyVoice();
{ }
voice->Stop();
voice->FlushSourceBuffers(); data_ = nullptr;
voice->DestroyVoice(); opened_ = false;
} playing_ = false;
}
coder_ = nullptr;
opened_ = false; bool Sound::IsPlaying() const
playing_ = false; {
} if (opened_)
{
bool Sound::IsPlaying() const if (!playing_)
{ return false;
if (opened_)
{ auto voice = GetNative<IXAudio2SourceVoice*>();
if (!playing_) if (!voice)
return false; return false;
auto voice = GetNativePtr<IXAudio2SourceVoice>(); XAUDIO2_VOICE_STATE state;
if (!voice) voice->GetState(&state);
return false; return !!state.BuffersQueued;
}
XAUDIO2_VOICE_STATE state; return false;
voice->GetState(&state); }
return !!state.BuffersQueued;
} float Sound::GetVolume() const
return false; {
} return volume_;
}
float Sound::GetVolume() const
{ void Sound::SetVolume(float volume)
return volume_; {
} if (volume_ == volume)
{
void Sound::SetVolume(float volume) return;
{ }
if (volume_ == volume) volume_ = volume;
{
return; auto voice = GetNative<IXAudio2SourceVoice*>();
} if (voice)
volume_ = volume; {
float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_);
auto voice = GetNativePtr<IXAudio2SourceVoice>(); actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL);
if (voice) voice->SetVolume(actual_volume);
{ }
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_;
void Sound::ResetVolume() volume_ += 1.f;
{ SetVolume(old_volume);
const float old_volume = volume_; }
volume_ += 1.f; SoundCallbackPtr Sound::GetCallbackChain()
SetVolume(old_volume); {
} class SoundCallbackChain : public SoundCallback
{
SoundCallbackPtr Sound::GetCallbackChain() public:
{ Sound* sound = nullptr;
class SoundCallbackChain : public SoundCallback
{ void OnStart(Sound*) override
public: {
Sound* sound = nullptr; for (auto& cb : sound->GetCallbacks())
{
void OnStart(Sound*) override if (cb)
{ {
for (auto& cb : sound->GetCallbacks()) cb->OnStart(sound);
{ }
if (cb) }
{ RemoveUsedCallbacks();
cb->OnStart(sound); }
}
} void OnLoopEnd(Sound*) override
RemoveUsedCallbacks(); {
} for (auto& cb : sound->GetCallbacks())
{
void OnLoopEnd(Sound*) override if (cb)
{ {
for (auto& cb : sound->GetCallbacks()) cb->OnLoopEnd(sound);
{ }
if (cb) }
{ RemoveUsedCallbacks();
cb->OnLoopEnd(sound); }
}
} void OnEnd(Sound*) override
RemoveUsedCallbacks(); {
} for (auto& cb : sound->GetCallbacks())
{
void OnEnd(Sound*) override if (cb)
{ {
for (auto& cb : sound->GetCallbacks()) cb->OnEnd(sound);
{ }
if (cb) }
{ RemoveUsedCallbacks();
cb->OnEnd(sound); }
}
} float OnVolumeChanged(Sound*, float volume) override
RemoveUsedCallbacks(); {
} float actual_volume = volume;
for (auto& cb : sound->GetCallbacks())
float OnVolumeChanged(Sound*, float volume) override {
{ if (cb)
float actual_volume = volume; {
for (auto& cb : sound->GetCallbacks()) actual_volume = cb->OnVolumeChanged(sound, volume);
{ }
if (cb) }
{ return actual_volume;
actual_volume = cb->OnVolumeChanged(sound, volume); }
}
} void RemoveUsedCallbacks()
return actual_volume; {
} auto& cbs = sound->GetCallbacks();
auto iter = cbs.begin();
void RemoveUsedCallbacks() while (iter != cbs.end())
{ {
auto& cbs = sound->GetCallbacks(); if (*iter == nullptr)
auto iter = cbs.begin(); {
while (iter != cbs.end()) iter = cbs.erase(iter);
{ }
if (*iter == nullptr) else
{ {
iter = cbs.erase(iter); iter++;
} }
else }
{ }
iter++; };
}
} if (!callback_chain_)
} {
}; auto chain = MakePtr<SoundCallbackChain>();
chain->sound = this;
if (!callback_chain_) callback_chain_ = chain;
{ }
auto chain = MakePtr<SoundCallbackChain>(); return callback_chain_;
chain->sound = this; }
callback_chain_ = chain;
} SoundCallbackPtr SoundCallback::OnStart(const Function<void(Sound* sound)>& cb)
return callback_chain_; {
} class SoundCallbackFunc : public SoundCallback
{
SoundCallbackPtr SoundCallback::OnStart(const Function<void(Sound* sound)>& cb) public:
{ Function<void(Sound* sound)> cb;
class SoundCallbackFunc : public SoundCallback
{ void OnStart(Sound* sound) override
public: {
Function<void(Sound* sound)> cb; if (cb)
{
void OnStart(Sound* sound) override cb(sound);
{ }
if (cb) }
{ };
cb(sound); auto ptr = MakePtr<SoundCallbackFunc>();
} ptr->cb = cb;
} return ptr;
}; }
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb; SoundCallbackPtr SoundCallback::OnLoopEnd(const Function<void(Sound* sound)>& cb)
return ptr; {
} class SoundCallbackFunc : public SoundCallback
{
SoundCallbackPtr SoundCallback::OnLoopEnd(const Function<void(Sound* sound)>& cb) public:
{ Function<void(Sound* sound)> cb;
class SoundCallbackFunc : public SoundCallback
{ void OnLoopEnd(Sound* sound) override
public: {
Function<void(Sound* sound)> cb; if (cb)
{
void OnLoopEnd(Sound* sound) override cb(sound);
{ }
if (cb) }
{ };
cb(sound); auto ptr = MakePtr<SoundCallbackFunc>();
} ptr->cb = cb;
} return ptr;
}; }
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb; SoundCallbackPtr SoundCallback::OnEnd(const Function<void(Sound* sound)>& cb)
return ptr; {
} class SoundCallbackFunc : public SoundCallback
{
SoundCallbackPtr SoundCallback::OnEnd(const Function<void(Sound* sound)>& cb) public:
{ Function<void(Sound* sound)> cb;
class SoundCallbackFunc : public SoundCallback
{ void OnEnd(Sound* sound) override
public: {
Function<void(Sound* sound)> cb; if (cb)
{
void OnEnd(Sound* sound) override cb(sound);
{ }
if (cb) }
{ };
cb(sound); auto ptr = MakePtr<SoundCallbackFunc>();
} ptr->cb = cb;
} return ptr;
}; }
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb; SoundCallbackPtr SoundCallback::OnVolumeChanged(const Function<float(Sound* sound, float volume)>& cb)
return ptr; {
} class SoundCallbackFunc : public SoundCallback
{
SoundCallbackPtr SoundCallback::OnVolumeChanged(const Function<float(Sound* sound, float volume)>& cb) public:
{ Function<float(Sound* sound, float volume)> cb;
class SoundCallbackFunc : public SoundCallback
{ float OnVolumeChanged(Sound* sound, float volume) override
public: {
Function<float(Sound* sound, float volume)> cb; if (cb)
{
float OnVolumeChanged(Sound* sound, float volume) override return cb(sound, volume);
{ }
if (cb) return volume;
{ }
return cb(sound, volume); };
} auto ptr = MakePtr<SoundCallbackFunc>();
return volume; ptr->cb = cb;
} return ptr;
}; }
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb; } // namespace audio
return ptr; } // namespace kiwano
}
} // namespace audio
} // namespace kiwano

View File

@ -1,204 +1,194 @@
// Copyright (c) 2016-2018 Kiwano - Nomango // Copyright (c) 2016-2018 Kiwano - Nomango
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights // in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is // copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: // furnished to do so, subject to the following conditions:
// //
// The above copyright notice and this permission notice shall be included in // The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software. // all copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 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 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#pragma once
#include <kiwano-audio/Transcoder.h>
#include <kiwano/core/Resource.h>
#include <kiwano/platform/NativeObject.hpp>
#include <xaudio2.h>
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<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 创建音频对象
/// @param res 本地音频文件路径
Sound(const String& file_path);
/// \~chinese
/// @brief 创建音频对象
/// @param res 音频资源
Sound(const Resource& res);
/// \~chinese #pragma once
/// @brief 创建音频对象 #include <kiwano/core/Resource.h>
/// @param data 音频数据 #include <kiwano-audio/AudioData.h>
/// @param metadata 音频元数据
Sound(const BinaryData& data, const AudioMetadata& metadata); namespace kiwano
{
/// \~chinese namespace audio
/// @brief 创建音频对象 {
/// @param transcoder 音频解码器 class AudioModule;
Sound(TranscoderPtr transcoder); class SoundPlayer;
Sound(); KGE_DECLARE_SMART_PTR(Sound);
KGE_DECLARE_SMART_PTR(SoundCallback);
virtual ~Sound();
/**
/// \~chinese * \addtogroup Audio
/// @brief 播放 * @{
/// @param loop_count 播放循环次数,设置 -1 为循环播放 */
void Play(int loop_count = 0);
/**
/// \~chinese * \~chinese
/// @brief 暂停 * @brief
void Pause(); */
class KGE_API SoundCallback : public NativeObject
/// \~chinese {
/// @brief 继续 public:
void Resume(); /// \~chinese
/// @brief 创建一个回调,在音频开始播放时执行
/// \~chinese static SoundCallbackPtr OnStart(const Function<void(Sound* sound)>& cb);
/// @brief 停止
void Stop(); /// \~chinese
/// @brief 创建一个回调,在音频循环结束时执行
/// \~chinese static SoundCallbackPtr OnLoopEnd(const Function<void(Sound* sound)>& cb);
/// @brief 关闭并销毁资源
void Close(); /// \~chinese
/// @brief 创建一个回调,在音频结束时执行
/// \~chinese static SoundCallbackPtr OnEnd(const Function<void(Sound* sound)>& cb);
/// @brief 是否正在播放
bool IsPlaying() const; /// \~chinese
/// @brief 创建一个回调,在音频修改音量时执行
/// \~chinese static SoundCallbackPtr OnVolumeChanged(const Function<float(Sound* sound, float volume)>& cb);
/// @brief 获取音量
float GetVolume() const; /// \~chinese
/// @brief 在音频开始播放时执行
/// \~chinese virtual inline void OnStart(Sound* sound) {}
/// @brief 设置音量
/// @param volume 音量大小1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量 /// \~chinese
void SetVolume(float volume); /// @brief 在音频循环结束时执行
virtual inline void OnLoopEnd(Sound* sound) {}
/// \~chinese
/// @brief 添加回调 /// \~chinese
void AddCallback(SoundCallbackPtr callback); /// @brief 在音频结束时执行
virtual inline void OnEnd(Sound* sound) {}
/// \~chinese
/// @brief 获取所有回调 /// \~chinese
List<SoundCallbackPtr>& GetCallbacks(); /// @brief 在音频修改音量时执行
virtual inline float OnVolumeChanged(Sound* sound, float volume)
/// \~chinese {
/// @brief 获取所有回调 return volume;
const List<SoundCallbackPtr>& GetCallbacks() const; }
};
protected:
/// \~chinese /**
/// @brief 加载音频 * \~chinese
/// @param transcoder 音频解码器 * @brief
bool Load(TranscoderPtr transcoder); */
class KGE_API Sound : public NativeObject
SoundCallbackPtr GetCallbackChain(); {
friend class AudioModule;
void ResetVolume(); friend class SoundPlayer;
private: public:
bool opened_; /// \~chinese
bool playing_; /// @brief 创建音频对象
float volume_; /// @param file_path 本地音频文件路径
TranscoderPtr coder_; Sound(const String& file_path);
SoundCallbackPtr callback_chain_; /// \~chinese
List<SoundCallbackPtr> callbacks_; /// @brief 创建音频对象
}; /// @param res 音频资源
/// @param ext 音频类型,决定了使用何种解码器
/** @} */ Sound(const Resource& res, const String& ext = "");
inline List<SoundCallbackPtr>& kiwano::audio::Sound::GetCallbacks() /// \~chinese
{ /// @brief 创建音频对象
return callbacks_; /// @param data 音频数据
} Sound(AudioDataPtr data);
inline const List<SoundCallbackPtr>& kiwano::audio::Sound::GetCallbacks() const Sound();
{
return callbacks_; virtual ~Sound();
}
/// \~chinese
inline void kiwano::audio::Sound::AddCallback(SoundCallbackPtr callback) /// @brief 播放
{ /// @param loop_count 播放循环次数,设置 -1 为循环播放
callbacks_.push_back(callback); void Play(int loop_count = 0);
}
/// \~chinese
} // namespace audio /// @brief 暂停
} // namespace kiwano 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<SoundCallbackPtr>& GetCallbacks();
/// \~chinese
/// @brief 获取所有回调
const List<SoundCallbackPtr>& GetCallbacks() const;
protected:
bool Load(AudioDataPtr data);
SoundCallbackPtr GetCallbackChain();
void ResetVolume();
private:
bool opened_;
bool playing_;
float volume_;
AudioDataPtr data_;
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

View File

@ -56,14 +56,14 @@ SoundPlayer::SoundPlayer()
SoundPlayer::~SoundPlayer() SoundPlayer::~SoundPlayer()
{} {}
TranscoderPtr SoundPlayer::Preload(const String& file_path) AudioDataPtr SoundPlayer::Preload(const String& file_path)
{ {
size_t hash_code = std::hash<String>{}(file_path); size_t hash_code = std::hash<String>{}(file_path);
if (cache_.count(hash_code)) if (cache_.count(hash_code))
{ {
return cache_.at(hash_code); return cache_.at(hash_code);
} }
TranscoderPtr ptr = MakePtr<Transcoder>(file_path); AudioDataPtr ptr = AudioModule::GetInstance().Decode(file_path);
if (ptr) if (ptr)
{ {
cache_.insert(std::make_pair(hash_code, ptr)); cache_.insert(std::make_pair(hash_code, ptr));
@ -71,14 +71,14 @@ TranscoderPtr SoundPlayer::Preload(const String& file_path)
return ptr; return ptr;
} }
TranscoderPtr SoundPlayer::Preload(const Resource& res) AudioDataPtr SoundPlayer::Preload(const Resource& res, const String& ext)
{ {
size_t hash_code = res.GetId(); size_t hash_code = res.GetId();
if (cache_.count(hash_code)) if (cache_.count(hash_code))
{ {
return cache_.at(hash_code); return cache_.at(hash_code);
} }
TranscoderPtr ptr = MakePtr<Transcoder>(res); AudioDataPtr ptr = AudioModule::GetInstance().Decode(res, ext);
if (ptr) if (ptr)
{ {
cache_.insert(std::make_pair(hash_code, 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<SoundCallbackPtr> callbacks) SoundPtr SoundPlayer::Play(const String& file_path, int loop_count)
{ {
TranscoderPtr transcoder = Preload(file_path); SoundPtr sound = new Sound(Preload(file_path));
SoundPtr sound = new Sound(transcoder);
for (const auto& cb : callbacks)
{
sound->AddCallback(cb);
}
Play(sound, loop_count); Play(sound, loop_count);
return sound; return sound;
} }
SoundPtr SoundPlayer::Play(const Resource& res, int loop_count, std::initializer_list<SoundCallbackPtr> callbacks) SoundPtr SoundPlayer::Play(const Resource& res, int loop_count)
{ {
TranscoderPtr transcoder = Preload(res); SoundPtr sound = new Sound(Preload(res));
SoundPtr sound = new Sound(transcoder);
for (const auto& cb : callbacks)
{
sound->AddCallback(cb);
}
Play(sound, loop_count); Play(sound, loop_count);
return sound; return sound;
} }

View File

@ -48,11 +48,11 @@ public:
/// \~chinese /// \~chinese
/// @brief 预加载音频 /// @brief 预加载音频
/// @details /// @details
TranscoderPtr Preload(const String& file_path); AudioDataPtr Preload(const String& file_path);
/// \~chinese /// \~chinese
/// @brief 预加载音频资源 /// @brief 预加载音频资源
TranscoderPtr Preload(const Resource& res); AudioDataPtr Preload(const Resource& res, const String& ext = "");
/// \~chinese /// \~chinese
/// @brief 播放音频 /// @brief 播放音频
@ -64,15 +64,13 @@ public:
/// @brief 播放音频 /// @brief 播放音频
/// @param file_path 本地音频文件路径 /// @param file_path 本地音频文件路径
/// @param loop_count 播放循环次数,设置 -1 为循环播放 /// @param loop_count 播放循环次数,设置 -1 为循环播放
/// @param callbacks ×¢²á»Øµ÷ SoundPtr Play(const String& file_path, int loop_count = 0);
SoundPtr Play(const String& file_path, int loop_count = 0, std::initializer_list<SoundCallbackPtr> callbacks = {});
/// \~chinese /// \~chinese
/// @brief 播放音频 /// @brief 播放音频
/// @param res 音频资源 /// @param res 音频资源
/// @param loop_count 播放循环次数,设置 -1 为循环播放 /// @param loop_count 播放循环次数,设置 -1 为循环播放
/// @param callbacks ×¢²á»Øµ÷ SoundPtr Play(const Resource& res, int loop_count = 0);
SoundPtr Play(const Resource& res, int loop_count = 0, std::initializer_list<SoundCallbackPtr> callbacks = {});
/// \~chinese /// \~chinese
/// @brief 暂停所有音频 /// @brief 暂停所有音频
@ -120,7 +118,7 @@ protected:
SoundList trash_; SoundList trash_;
SoundCallbackPtr callback_; SoundCallbackPtr callback_;
UnorderedMap<size_t, TranscoderPtr> cache_; UnorderedMap<size_t, AudioDataPtr> cache_;
}; };
/** @} */ /** @} */

View File

@ -1,110 +1,56 @@
// Copyright (c) 2016-2018 Kiwano - Nomango // Copyright (c) 2016-2018 Kiwano - Nomango
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights // in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is // copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: // furnished to do so, subject to the following conditions:
// //
// The above copyright notice and this permission notice shall be included in // The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software. // all copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 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 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#pragma once #pragma once
#include <kiwano/core/Resource.h> #include <kiwano/core/Resource.h>
#include <kiwano/base/ObjectBase.h> #include <kiwano-audio/AudioData.h>
namespace kiwano namespace kiwano
{ {
namespace audio namespace audio
{ {
class AudioModule;
KGE_DECLARE_SMART_PTR(Transcoder);
KGE_DECLARE_SMART_PTR(Transcoder);
/**
/** * \addtogroup Audio
* \addtogroup Audio * @{
* @{
*/ */
/** /**
* \~chinese * \~chinese
* @brief * @brief ÒôƵ½âÂëÆ÷
*/ */
enum class AudioFormat 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); } // namespace audio
} // namespace kiwano
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<uint8_t[]> raw_;
BinaryData data_;
AudioMetadata metadata_;
};
/** @} */
} // namespace audio
} // namespace kiwano

View File

@ -55,45 +55,6 @@ XAudio2::XAudio2()
KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load xaudio2.dll failed"); 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<PFN_MFStartup>("MFStartup");
MFShutdown = mfplat.GetProcess<PFN_MFShutdown>("MFShutdown");
MFCreateMediaType = mfplat.GetProcess<PFN_MFCreateMediaType>("MFCreateMediaType");
MFCreateWaveFormatExFromMFMediaType =
mfplat.GetProcess<PFN_MFCreateWaveFormatExFromMFMediaType>("MFCreateWaveFormatExFromMFMediaType");
MFCreateMFByteStreamOnStream =
mfplat.GetProcess<PFN_MFCreateMFByteStreamOnStream>("MFCreateMFByteStreamOnStream");
}
else
{
KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfplat.dll failed");
}
if (mfreadwrite.Load("Mfreadwrite.dll"))
{
MFCreateSourceReaderFromURL =
mfreadwrite.GetProcess<PFN_MFCreateSourceReaderFromURL>("MFCreateSourceReaderFromURL");
MFCreateSourceReaderFromByteStream =
mfreadwrite.GetProcess<PFN_MFCreateSourceReaderFromByteStream>("MFCreateSourceReaderFromByteStream");
}
else
{
KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Load Mfreadwrite.dll failed");
}
}
} // namespace dlls } // namespace dlls
} // namespace audio } // namespace audio
} // namespace kiwano } // namespace kiwano

View File

@ -20,9 +20,6 @@
#pragma once #pragma once
#include <kiwano/core/Library.h> #include <kiwano/core/Library.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <xaudio2.h> #include <xaudio2.h>
#ifndef KGE_DOXYGEN_DO_NOT_INCLUDE #ifndef KGE_DOXYGEN_DO_NOT_INCLUDE
@ -55,42 +52,6 @@ private:
Library xaudio2; 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 dlls
} // namespace audio } // namespace audio
} // namespace kiwano } // namespace kiwano

View File

@ -130,6 +130,16 @@ String FileSystem::GetFullPathForFile(const String& file) const
return ""; 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) void FileSystem::AddFileLookupRule(const String& key, const String& file_path)
{ {
file_lookup_dict_.emplace(key, ConvertPathFormat(file_path)); file_lookup_dict_.emplace(key, ConvertPathFormat(file_path));

View File

@ -54,6 +54,14 @@ public:
*/ */
String GetFullPathForFile(const String& file) const; String GetFullPathForFile(const String& file) const;
/**
* \~chinese
* @brief
* @param file
* @return .
*/
String GetFileExt(const String& file) const;
/** /**
* \~chinese * \~chinese
* @brief * @brief

View File

@ -40,9 +40,6 @@ public:
template <class _Ty> template <class _Ty>
_Ty GetNative() const; _Ty GetNative() const;
template<class _Ty>
_Ty* GetNativePtr() const;
void SetNative(const Any& native); void SetNative(const Any& native);
void ResetNative(); void ResetNative();
@ -65,17 +62,7 @@ inline _Ty NativeObject::GetNative() const
{ {
return native_.Cast<_Ty>(); return native_.Cast<_Ty>();
} }
return nullptr; return _Ty{};
}
template <class _Ty>
inline _Ty* NativeObject::GetNativePtr() const
{
if (native_.HasValue())
{
return native_.Cast<_Ty*>();
}
return nullptr;
} }
inline void NativeObject::SetNative(const Any& native) inline void NativeObject::SetNative(const Any& native)