feat: support register custom audio transcoder
This commit is contained in:
parent
7a1c565a92
commit
641f78b41e
|
|
@ -1,9 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<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\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\SoundPlayer.h" />
|
||||
<ClInclude Include="..\..\src\kiwano-audio\Transcoder.h" />
|
||||
|
|
@ -27,11 +30,13 @@
|
|||
</ProjectConfiguration>
|
||||
</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\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\SoundPlayer.cpp" />
|
||||
<ClCompile Include="..\..\src\kiwano-audio\Transcoder.cpp" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{1B97937D-8184-426C-BE71-29A163DC76C9}</ProjectGuid>
|
||||
|
|
|
|||
|
|
@ -5,14 +5,32 @@
|
|||
<ClInclude Include="..\..\src\kiwano-audio\Sound.h" />
|
||||
<ClInclude Include="..\..\src\kiwano-audio\SoundPlayer.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\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>
|
||||
<ClCompile Include="..\..\src\kiwano-audio\Sound.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\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>
|
||||
</Project>
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 <kiwano-audio/AudioModule.h>
|
||||
#include <kiwano-audio/libraries.h>
|
||||
#include <kiwano/core/Exception.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
|
||||
{
|
||||
|
|
@ -89,18 +90,18 @@ 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<MFTranscoder>());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
WAVEFORMATEX* wave_format_ptr = &wave_format;
|
||||
if (metadata.extra_data != nullptr)
|
||||
WAVEFORMATEX* wave_fmt = data->GetNative<WAVEFORMATEX*>();
|
||||
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))
|
||||
|
|
@ -150,7 +151,7 @@ bool AudioModule::CreateSound(Sound& sound, const AudioMetadata& metadata)
|
|||
auto callback = const_cast<VoiceCallback*>(chain->GetNative().CastPtr<VoiceCallback>());
|
||||
|
||||
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();
|
||||
|
|
@ -180,5 +181,50 @@ 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
|
||||
|
|
|
|||
|
|
@ -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<String, TranscoderPtr> registered_transcoders_;
|
||||
};
|
||||
|
||||
/** @} */
|
||||
|
|
|
|||
|
|
@ -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 <kiwano-audio/Transcoder.h>
|
||||
#include <kiwano-audio/libraries.h>
|
||||
#include <kiwano-audio/MediaFoundation/Transcoder.h>
|
||||
#include <kiwano-audio/MediaFoundation/mflib.h>
|
||||
#include <kiwano/core/Common.h>
|
||||
#include <kiwano/core/Resource.h>
|
||||
#include <kiwano/utils/Logger.h>
|
||||
#include <kiwano/platform/win32/ComPtr.hpp>
|
||||
#include <kiwano/platform/win32/libraries.h>
|
||||
#include <kiwano/platform/FileSystem.h>
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
namespace kiwano
|
||||
{
|
||||
namespace audio
|
||||
{
|
||||
|
||||
HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std::unique_ptr<uint8_t[]>& output_data,
|
||||
uint32_t& output_size);
|
||||
HRESULT ReadSource(AudioDataPtr& output, IMFSourceReader* reader);
|
||||
|
||||
Transcoder::Transcoder(const String& file_path)
|
||||
MFTranscoder::MFTranscoder()
|
||||
{
|
||||
Load(file_path);
|
||||
HRESULT hr = dlls::MediaFoundation::Get().MFStartup(MF_VERSION, MFSTARTUP_FULL);
|
||||
KGE_THROW_IF_FAILED(hr, "Initialize Media Foundation failed!");
|
||||
}
|
||||
|
||||
Transcoder::Transcoder(const Resource& res)
|
||||
MFTranscoder::~MFTranscoder()
|
||||
{
|
||||
Load(res);
|
||||
dlls::MediaFoundation::Get().MFShutdown();
|
||||
}
|
||||
|
||||
Transcoder::Transcoder(const BinaryData& data, const AudioMetadata& metadata)
|
||||
AudioDataPtr MFTranscoder::Decode(const String& file_path)
|
||||
{
|
||||
Load(data, metadata);
|
||||
}
|
||||
|
||||
Transcoder::~Transcoder()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
AudioMetadata Transcoder::GetMetadata() const
|
||||
{
|
||||
return metadata_;
|
||||
}
|
||||
|
||||
BinaryData Transcoder::GetData() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
void Transcoder::Clear()
|
||||
{
|
||||
if (metadata_.extra_data != nullptr)
|
||||
{
|
||||
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)
|
||||
{
|
||||
data_ = data;
|
||||
metadata_ = metadata;
|
||||
return true;
|
||||
}
|
||||
|
||||
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<IMFSourceReader> reader;
|
||||
|
||||
HRESULT hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromURL(path.c_str(), nullptr, &reader);
|
||||
WideString path = strings::NarrowToWide(file_path);
|
||||
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))
|
||||
{
|
||||
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<const BYTE*>(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<uint8_t[]>& output_data,
|
||||
uint32_t& output_size)
|
||||
class MFAudioData : public AudioData
|
||||
{
|
||||
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;
|
||||
|
||||
|
|
@ -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))
|
||||
|
|
@ -257,7 +212,7 @@ HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std:
|
|||
DWORD flags = 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<IMFMediaBuffer> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
#include <kiwano-audio/AudioModule.h>
|
||||
#include <kiwano-audio/Sound.h>
|
||||
#include <kiwano/utils/Logger.h>
|
||||
#include <xaudio2.h>
|
||||
|
||||
namespace kiwano
|
||||
{
|
||||
|
|
@ -30,27 +31,19 @@ namespace audio
|
|||
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()
|
||||
{
|
||||
TranscoderPtr transcoder = MakePtr<Transcoder>(res);
|
||||
Load(transcoder);
|
||||
Load(AudioModule::GetInstance().Decode(res, ext));
|
||||
}
|
||||
|
||||
Sound::Sound(const BinaryData& data, const AudioMetadata& metadata)
|
||||
{
|
||||
TranscoderPtr transcoder = MakePtr<Transcoder>(data, metadata);
|
||||
Load(transcoder);
|
||||
}
|
||||
|
||||
Sound::Sound(TranscoderPtr transcoder)
|
||||
Sound::Sound(AudioDataPtr data)
|
||||
: Sound()
|
||||
{
|
||||
Load(transcoder);
|
||||
Load(data);
|
||||
}
|
||||
|
||||
Sound::Sound()
|
||||
|
|
@ -65,22 +58,18 @@ Sound::~Sound()
|
|||
Close();
|
||||
}
|
||||
|
||||
bool Sound::Load(TranscoderPtr transcoder)
|
||||
bool Sound::Load(AudioDataPtr data)
|
||||
{
|
||||
if (!transcoder->IsValid())
|
||||
if (!data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
BinaryData data_;
|
||||
AudioMetadata metadata_;
|
||||
|
||||
if (opened_)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
if (!AudioModule::GetInstance().CreateSound(*this, transcoder->GetMetadata()))
|
||||
if (!AudioModule::GetInstance().CreateSound(*this, data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -88,7 +77,7 @@ bool Sound::Load(TranscoderPtr transcoder)
|
|||
// reset volume
|
||||
ResetVolume();
|
||||
|
||||
coder_ = transcoder;
|
||||
data_ = data;
|
||||
opened_ = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -101,7 +90,7 @@ void Sound::Play(int loop_count)
|
|||
return;
|
||||
}
|
||||
|
||||
auto voice = GetNativePtr<IXAudio2SourceVoice>();
|
||||
auto voice = GetNative<IXAudio2SourceVoice*>();
|
||||
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
|
||||
|
||||
// if sound stream is not empty, stop() will clear it
|
||||
|
|
@ -113,7 +102,7 @@ void Sound::Play(int loop_count)
|
|||
// clamp loop count
|
||||
loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1);
|
||||
|
||||
auto data = coder_->GetData();
|
||||
auto data = data_->GetData();
|
||||
|
||||
XAUDIO2_BUFFER xaudio2_buffer = { 0 };
|
||||
xaudio2_buffer.pAudioData = reinterpret_cast<BYTE*>(data.buffer);
|
||||
|
|
@ -137,7 +126,7 @@ void Sound::Play(int loop_count)
|
|||
|
||||
void Sound::Pause()
|
||||
{
|
||||
auto voice = GetNativePtr<IXAudio2SourceVoice>();
|
||||
auto voice = GetNative<IXAudio2SourceVoice*>();
|
||||
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
|
||||
|
||||
HRESULT hr = voice->Stop();
|
||||
|
|
@ -152,7 +141,7 @@ void Sound::Pause()
|
|||
|
||||
void Sound::Resume()
|
||||
{
|
||||
auto voice = GetNativePtr<IXAudio2SourceVoice>();
|
||||
auto voice = GetNative<IXAudio2SourceVoice*>();
|
||||
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
|
||||
|
||||
HRESULT hr = voice->Start();
|
||||
|
|
@ -167,7 +156,7 @@ void Sound::Resume()
|
|||
|
||||
void Sound::Stop()
|
||||
{
|
||||
auto voice = GetNativePtr<IXAudio2SourceVoice>();
|
||||
auto voice = GetNative<IXAudio2SourceVoice*>();
|
||||
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
|
||||
|
||||
HRESULT hr = voice->Stop();
|
||||
|
|
@ -189,7 +178,7 @@ void Sound::Stop()
|
|||
|
||||
void Sound::Close()
|
||||
{
|
||||
auto voice = GetNativePtr<IXAudio2SourceVoice>();
|
||||
auto voice = GetNative<IXAudio2SourceVoice*>();
|
||||
if (voice)
|
||||
{
|
||||
voice->Stop();
|
||||
|
|
@ -197,7 +186,7 @@ void Sound::Close()
|
|||
voice->DestroyVoice();
|
||||
}
|
||||
|
||||
coder_ = nullptr;
|
||||
data_ = nullptr;
|
||||
opened_ = false;
|
||||
playing_ = false;
|
||||
}
|
||||
|
|
@ -209,7 +198,7 @@ bool Sound::IsPlaying() const
|
|||
if (!playing_)
|
||||
return false;
|
||||
|
||||
auto voice = GetNativePtr<IXAudio2SourceVoice>();
|
||||
auto voice = GetNative<IXAudio2SourceVoice*>();
|
||||
if (!voice)
|
||||
return false;
|
||||
|
||||
|
|
@ -233,7 +222,7 @@ void Sound::SetVolume(float volume)
|
|||
}
|
||||
volume_ = volume;
|
||||
|
||||
auto voice = GetNativePtr<IXAudio2SourceVoice>();
|
||||
auto voice = GetNative<IXAudio2SourceVoice*>();
|
||||
if (voice)
|
||||
{
|
||||
float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_);
|
||||
|
|
|
|||
|
|
@ -19,10 +19,8 @@
|
|||
// THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
#include <kiwano-audio/Transcoder.h>
|
||||
#include <kiwano/core/Resource.h>
|
||||
#include <kiwano/platform/NativeObject.hpp>
|
||||
#include <xaudio2.h>
|
||||
#include <kiwano-audio/AudioData.h>
|
||||
|
||||
namespace kiwano
|
||||
{
|
||||
|
|
@ -94,24 +92,19 @@ class KGE_API Sound : public NativeObject
|
|||
public:
|
||||
/// \~chinese
|
||||
/// @brief 创建音频对象
|
||||
/// @param res 本地音频文件路径
|
||||
/// @param file_path 本地音频文件路径
|
||||
Sound(const String& file_path);
|
||||
|
||||
/// \~chinese
|
||||
/// @brief 创建音频对象
|
||||
/// @param res 音频资源
|
||||
Sound(const Resource& res);
|
||||
/// @param ext 音频类型,决定了使用何种解码器
|
||||
Sound(const Resource& res, const String& ext = "");
|
||||
|
||||
/// \~chinese
|
||||
/// @brief 创建音频对象
|
||||
/// @param data 音频数据
|
||||
/// @param metadata 音频元数据
|
||||
Sound(const BinaryData& data, const AudioMetadata& metadata);
|
||||
|
||||
/// \~chinese
|
||||
/// @brief 创建音频对象
|
||||
/// @param transcoder 音频解码器
|
||||
Sound(TranscoderPtr transcoder);
|
||||
Sound(AudioDataPtr data);
|
||||
|
||||
Sound();
|
||||
|
||||
|
|
@ -164,20 +157,17 @@ public:
|
|||
const List<SoundCallbackPtr>& GetCallbacks() const;
|
||||
|
||||
protected:
|
||||
/// \~chinese
|
||||
/// @brief 加载音频
|
||||
/// @param transcoder 音频解码器
|
||||
bool Load(TranscoderPtr transcoder);
|
||||
bool Load(AudioDataPtr data);
|
||||
|
||||
SoundCallbackPtr GetCallbackChain();
|
||||
|
||||
void ResetVolume();
|
||||
|
||||
private:
|
||||
bool opened_;
|
||||
bool playing_;
|
||||
float volume_;
|
||||
TranscoderPtr coder_;
|
||||
bool opened_;
|
||||
bool playing_;
|
||||
float volume_;
|
||||
AudioDataPtr data_;
|
||||
|
||||
SoundCallbackPtr callback_chain_;
|
||||
List<SoundCallbackPtr> callbacks_;
|
||||
|
|
|
|||
|
|
@ -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<String>{}(file_path);
|
||||
if (cache_.count(hash_code))
|
||||
{
|
||||
return cache_.at(hash_code);
|
||||
}
|
||||
TranscoderPtr ptr = MakePtr<Transcoder>(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<Transcoder>(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<SoundCallbackPtr> 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<SoundCallbackPtr> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SoundCallbackPtr> 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<SoundCallbackPtr> callbacks = {});
|
||||
SoundPtr Play(const Resource& res, int loop_count = 0);
|
||||
|
||||
/// \~chinese
|
||||
/// @brief 暂停所有音频
|
||||
|
|
@ -120,7 +118,7 @@ protected:
|
|||
SoundList trash_;
|
||||
SoundCallbackPtr callback_;
|
||||
|
||||
UnorderedMap<size_t, TranscoderPtr> cache_;
|
||||
UnorderedMap<size_t, AudioDataPtr> cache_;
|
||||
};
|
||||
|
||||
/** @} */
|
||||
|
|
|
|||
|
|
@ -20,13 +20,12 @@
|
|||
|
||||
#pragma once
|
||||
#include <kiwano/core/Resource.h>
|
||||
#include <kiwano/base/ObjectBase.h>
|
||||
#include <kiwano-audio/AudioData.h>
|
||||
|
||||
namespace kiwano
|
||||
{
|
||||
namespace audio
|
||||
{
|
||||
class AudioModule;
|
||||
|
||||
KGE_DECLARE_SMART_PTR(Transcoder);
|
||||
|
||||
|
|
@ -35,73 +34,20 @@ KGE_DECLARE_SMART_PTR(Transcoder);
|
|||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* \~chinese
|
||||
* @brief 音频格式
|
||||
*/
|
||||
enum class AudioFormat
|
||||
{
|
||||
PCM,
|
||||
};
|
||||
|
||||
/**
|
||||
* \~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() = default;
|
||||
|
||||
Transcoder(const Resource& res);
|
||||
virtual ~Transcoder() = default;
|
||||
|
||||
Transcoder(const BinaryData& data, const AudioMetadata& metadata);
|
||||
virtual AudioDataPtr Decode(const String& file_path) = 0;
|
||||
|
||||
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_;
|
||||
virtual AudioDataPtr Decode(const Resource& res) = 0;
|
||||
};
|
||||
|
||||
/** @} */
|
||||
|
|
|
|||
|
|
@ -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<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
|
||||
|
|
|
|||
|
|
@ -20,9 +20,6 @@
|
|||
|
||||
#pragma once
|
||||
#include <kiwano/core/Library.h>
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
#include <xaudio2.h>
|
||||
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -54,6 +54,14 @@ public:
|
|||
*/
|
||||
String GetFullPathForFile(const String& file) const;
|
||||
|
||||
/**
|
||||
* \~chinese
|
||||
* @brief 获取文件拓展名
|
||||
* @param file 文件路径
|
||||
* @return 文件拓展名,不包含 . 符号
|
||||
*/
|
||||
String GetFileExt(const String& file) const;
|
||||
|
||||
/**
|
||||
* \~chinese
|
||||
* @brief 添加文件路径查找字典规则
|
||||
|
|
|
|||
|
|
@ -40,9 +40,6 @@ public:
|
|||
template <class _Ty>
|
||||
_Ty GetNative() const;
|
||||
|
||||
template<class _Ty>
|
||||
_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 <class _Ty>
|
||||
inline _Ty* NativeObject::GetNativePtr() const
|
||||
{
|
||||
if (native_.HasValue())
|
||||
{
|
||||
return native_.Cast<_Ty*>();
|
||||
}
|
||||
return nullptr;
|
||||
return _Ty{};
|
||||
}
|
||||
|
||||
inline void NativeObject::SetNative(const Any& native)
|
||||
|
|
|
|||
Loading…
Reference in New Issue