[deploy] feat(audio): support raw data sound

This commit is contained in:
Haibo 2023-10-05 18:17:04 +08:00
parent 8ffd29eb38
commit f861f3a5ab
7 changed files with 231 additions and 186 deletions

View File

@ -22,7 +22,6 @@
#include <kiwano-audio/libraries.h>
#include <kiwano/core/Exception.h>
#include <kiwano/utils/Logger.h>
#include <kiwano/platform/FileSystem.h>
namespace kiwano
{
@ -68,6 +67,16 @@ public:
}
};
WORD ConvertWaveFormat(AudioFormat format)
{
switch (format)
{
case kiwano::audio::AudioFormat::PCM:
return WAVE_FORMAT_PCM;
}
return 0;
}
AudioModule::AudioModule()
: x_audio2_(nullptr)
, mastering_voice_(nullptr)
@ -114,47 +123,25 @@ void AudioModule::DestroyModule()
dlls::MediaFoundation::Get().MFShutdown();
}
TranscoderPtr AudioModule::CreateTranscoder(const String& file_path)
{
if (!FileSystem::GetInstance().IsFileExists(file_path))
{
KGE_WARNF("Media file '%s' not found", file_path.c_str());
return nullptr;
}
String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path);
auto ptr = MakePtr<Transcoder>();
HRESULT hr = ptr->LoadMediaFile(full_path);
if (FAILED(hr))
{
KGE_ERRORF("Load media file failed with HRESULT of %08X", hr);
return nullptr;
}
return ptr;
}
TranscoderPtr AudioModule::CreateTranscoder(const Resource& res)
{
auto ptr = MakePtr<Transcoder>();
HRESULT hr = ptr->LoadMediaResource(res);
if (FAILED(hr))
{
KGE_ERRORF("Load media resource failed with HRESULT of %08X", hr);
return nullptr;
}
return ptr;
}
bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder)
bool AudioModule::CreateSound(Sound& sound, const AudioMetadata& metadata)
{
KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!");
HRESULT hr = S_OK;
auto buffer = transcoder->GetBuffer();
if (buffer.format == nullptr)
hr = E_INVALIDARG;
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)
{
wave_format_ptr = reinterpret_cast<WAVEFORMATEX*>(metadata.extra_data);
}
if (SUCCEEDED(hr))
{
@ -163,7 +150,7 @@ bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder)
auto callback = const_cast<VoiceCallback*>(chain->GetNative().CastPtr<VoiceCallback>());
IXAudio2SourceVoice* voice = nullptr;
hr = x_audio2_->CreateSourceVoice(&voice, buffer.format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback);
hr = x_audio2_->CreateSourceVoice(&voice, wave_format_ptr, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback);
if (SUCCEEDED(hr))
{
sound.Close();

View File

@ -59,17 +59,9 @@ public:
/// @brief 关闭音频设备
void Close();
/// \~chinese
/// @brief 创建音频解码器
TranscoderPtr CreateTranscoder(const String& file_path);
/// \~chinese
/// @brief 创建音频解码器
TranscoderPtr CreateTranscoder(const Resource& res);
/// \~chinese
/// @brief 创建音频
bool CreateSound(Sound& sound, TranscoderPtr transcoder);
bool CreateSound(Sound& sound, const AudioMetadata& metadata);
public:
void SetupModule() override;

View File

@ -30,13 +30,21 @@ namespace audio
Sound::Sound(const String& file_path)
: Sound()
{
Load(file_path);
TranscoderPtr transcoder = MakePtr<Transcoder>(file_path);
Load(transcoder);
}
Sound::Sound(const Resource& res)
: Sound()
{
Load(res);
TranscoderPtr transcoder = MakePtr<Transcoder>(res);
Load(transcoder);
}
Sound::Sound(const BinaryData& data, const AudioMetadata& metadata)
{
TranscoderPtr transcoder = MakePtr<Transcoder>(data, metadata);
Load(transcoder);
}
Sound::Sound(TranscoderPtr transcoder)
@ -57,43 +65,22 @@ Sound::~Sound()
Close();
}
bool Sound::Load(const String& file_path)
{
if (opened_)
{
Close();
}
TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(file_path);
if (!transcoder)
{
return false;
}
return Load(transcoder);
}
bool Sound::Load(const Resource& res)
{
if (opened_)
{
Close();
}
TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(res);
if (!transcoder)
{
return false;
}
return Load(transcoder);
}
bool Sound::Load(TranscoderPtr transcoder)
{
if (!transcoder->IsValid())
{
return false;
}
BinaryData data_;
AudioMetadata metadata_;
if (opened_)
{
Close();
}
if (!AudioModule::GetInstance().CreateSound(*this, transcoder))
if (!AudioModule::GetInstance().CreateSound(*this, transcoder->GetMetadata()))
{
return false;
}
@ -126,12 +113,12 @@ 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 buffer = coder_->GetBuffer();
auto data = coder_->GetData();
XAUDIO2_BUFFER xaudio2_buffer = { 0 };
xaudio2_buffer.pAudioData = buffer.data;
xaudio2_buffer.pAudioData = reinterpret_cast<BYTE*>(data.buffer);
xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM;
xaudio2_buffer.AudioBytes = buffer.size;
xaudio2_buffer.AudioBytes = UINT32(data.size);
xaudio2_buffer.LoopCount = static_cast<uint32_t>(loop_count);
HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer);
@ -144,6 +131,10 @@ void Sound::Play(int loop_count)
{
KGE_ERRORF("Submitting source buffer failed with HRESULT of %08X", hr);
}
else
{
KGE_LOG("success!!");
}
playing_ = SUCCEEDED(hr);
}
@ -246,13 +237,13 @@ void Sound::SetVolume(float volume)
}
volume_ = volume;
float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_);
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL);
voice->SetVolume(actual_volume);
if (voice)
{
float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_);
actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL);
voice->SetVolume(actual_volume);
}
}
void Sound::ResetVolume()

View File

@ -101,6 +101,12 @@ public:
/// @brief 创建音频对象
/// @param res 音频资源
Sound(const Resource& res);
/// \~chinese
/// @brief 创建音频对象
/// @param data 音频数据
/// @param metadata 音频元数据
Sound(const BinaryData& data, const AudioMetadata& metadata);
/// \~chinese
/// @brief 创建音频对象
@ -158,16 +164,6 @@ public:
const List<SoundCallbackPtr>& GetCallbacks() const;
protected:
/// \~chinese
/// @brief 加载本地音频文件
/// @param res 本地音频文件路径
bool Load(const String& file_path);
/// \~chinese
/// @brief 加载音频资源
/// @param res 音频资源
bool Load(const Resource& res);
/// \~chinese
/// @brief 加载音频
/// @param transcoder 音频解码器

View File

@ -63,7 +63,7 @@ TranscoderPtr SoundPlayer::Preload(const String& file_path)
{
return cache_.at(hash_code);
}
TranscoderPtr ptr = AudioModule::GetInstance().CreateTranscoder(file_path);
TranscoderPtr ptr = MakePtr<Transcoder>(file_path);
if (ptr)
{
cache_.insert(std::make_pair(hash_code, ptr));
@ -78,7 +78,7 @@ TranscoderPtr SoundPlayer::Preload(const Resource& res)
{
return cache_.at(hash_code);
}
TranscoderPtr ptr = AudioModule::GetInstance().CreateTranscoder(res);
TranscoderPtr ptr = MakePtr<Transcoder>(res);
if (ptr)
{
cache_.insert(std::make_pair(hash_code, ptr));

View File

@ -29,64 +29,102 @@
#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);
Transcoder::Transcoder(const String& file_path)
{
Transcoder::Transcoder()
: wave_format_(nullptr)
, wave_data_(nullptr)
, wave_size_(0)
Load(file_path);
}
Transcoder::Transcoder(const Resource& res)
{
Load(res);
}
Transcoder::Transcoder(const BinaryData& data, const AudioMetadata& metadata)
{
Load(data, metadata);
}
Transcoder::~Transcoder()
{
ClearBuffer();
Clear();
}
AudioMetadata Transcoder::GetMetadata() const
{
return metadata_;
}
Transcoder::Buffer Transcoder::GetBuffer() const
BinaryData Transcoder::GetData() const
{
return Buffer{ wave_data_, wave_size_, wave_format_ };
return data_;
}
void Transcoder::ClearBuffer()
void Transcoder::Clear()
{
if (wave_format_)
if (metadata_.extra_data != nullptr)
{
::CoTaskMemFree(wave_format_);
wave_format_ = 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;
}
if (wave_data_)
{
delete[] wave_data_;
wave_data_ = nullptr;
}
String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path);
WideString path = strings::NarrowToWide(full_path);
wave_size_ = 0;
}
HRESULT Transcoder::LoadMediaFile(const String& file_path)
{
HRESULT hr = S_OK;
WideString path = strings::NarrowToWide(file_path);
ComPtr<IMFSourceReader> reader;
hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromURL(path.c_str(), nullptr, &reader);
ComPtr<IMFSourceReader> reader;
HRESULT hr = dlls::MediaFoundation::Get().MFCreateSourceReaderFromURL(path.c_str(), nullptr, &reader);
if (SUCCEEDED(hr))
{
hr = ReadSource(reader.Get());
}
hr = ReadSource(reader.Get(), metadata_, raw_, data_.size);
return hr;
if (SUCCEEDED(hr))
{
data_.buffer = raw_.get();
}
}
if (FAILED(hr))
{
Fail(strings::Format("%s failed (%#x): %s", __FUNCTION__, hr, "Load audio failed"));
return false;
}
return true;
}
HRESULT Transcoder::LoadMediaResource(const Resource& res)
bool Transcoder::Load(const Resource& res)
{
HRESULT hr = S_OK;
@ -97,7 +135,8 @@ HRESULT Transcoder::LoadMediaResource(const Resource& res)
BinaryData data = res.GetData();
if (!data.IsValid())
{
return E_FAIL;
Fail("invalid audio data");
return false;
}
stream = win32::dlls::Shlwapi::Get().SHCreateMemStream(static_cast<const BYTE*>(data.buffer),
@ -105,8 +144,8 @@ HRESULT Transcoder::LoadMediaResource(const Resource& res)
if (stream == nullptr)
{
KGE_ERRORF("SHCreateMemStream failed");
return E_OUTOFMEMORY;
Fail("SHCreateMemStream failed");
return false;
}
if (SUCCEEDED(hr))
@ -121,16 +160,26 @@ HRESULT Transcoder::LoadMediaResource(const Resource& res)
if (SUCCEEDED(hr))
{
hr = ReadSource(reader.Get());
hr = ReadSource(reader.Get(), metadata_, raw_, data_.size);
if (SUCCEEDED(hr))
{
data_.buffer = raw_.get();
}
}
return hr;
}
if (FAILED(hr))
{
Fail(strings::Format("%s failed (%#x): %s", __FUNCTION__, hr, "Load audio failed"));
return false;
}
return true;
}
HRESULT Transcoder::ReadSource(IMFSourceReader* reader)
HRESULT ReadSource(IMFSourceReader* reader, AudioMetadata& output_metadata, std::unique_ptr<uint8_t[]>& output_data,
uint32_t& output_size)
{
HRESULT hr = S_OK;
DWORD max_stream_size = 0;
HRESULT hr = S_OK;
ComPtr<IMFMediaType> partial_type;
ComPtr<IMFMediaType> uncompressed_type;
@ -166,23 +215,39 @@ HRESULT Transcoder::ReadSource(IMFSourceReader* reader)
}
// 获取 WAVEFORMAT 数据
WAVEFORMATEX* wave_format = nullptr;
if (SUCCEEDED(hr))
{
uint32_t size = 0;
hr = dlls::MediaFoundation::Get().MFCreateWaveFormatExFromMFMediaType(
uncompressed_type.Get(), &wave_format_, &size, (DWORD)MFWaveFormatExConvertFlag_Normal);
hr = dlls::MediaFoundation::Get().MFCreateWaveFormatExFromMFMediaType(
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))
{
PROPVARIANT prop;
PropVariantInit(&prop);
hr = reader->GetPresentationAttribute((DWORD)MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &prop);
LONGLONG duration = prop.uhVal.QuadPart;
max_stream_size = static_cast<DWORD>((duration * wave_format_->nAvgBytesPerSec) / 10000000 + 1);
hr = reader->GetPresentationAttribute((DWORD)MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &prop);
if (SUCCEEDED(hr))
{
LONGLONG duration = prop.uhVal.QuadPart;
max_stream_size = static_cast<DWORD>((duration * wave_format->nAvgBytesPerSec) / 10000000 + 1);
}
PropVariantClear(&prop);
}
@ -190,13 +255,14 @@ HRESULT Transcoder::ReadSource(IMFSourceReader* reader)
if (SUCCEEDED(hr))
{
DWORD flags = 0;
DWORD position = 0;
BYTE* data = new (std::nothrow) BYTE[max_stream_size];
DWORD position = 0;
output_data = std::make_unique<uint8_t[]>(max_stream_size);
ComPtr<IMFSample> sample;
ComPtr<IMFMediaBuffer> buffer;
if (data == nullptr)
if (output_data == nullptr)
{
KGE_ERRORF("Low memory");
hr = E_OUTOFMEMORY;
@ -236,7 +302,7 @@ HRESULT Transcoder::ReadSource(IMFSourceReader* reader)
if (SUCCEEDED(hr))
{
::memcpy(data + position, audio_data, sample_buffer_length);
::memcpy(output_data.get() + position, audio_data, sample_buffer_length);
position += sample_buffer_length;
hr = buffer->Unlock();
}
@ -253,17 +319,15 @@ HRESULT Transcoder::ReadSource(IMFSourceReader* reader)
if (SUCCEEDED(hr))
{
wave_data_ = data;
wave_size_ = position;
output_size = uint32_t(position);
}
else
{
delete[] data;
data = nullptr;
output_data.reset();
output_size = 0;
}
}
}
return hr;
}
} // namespace audio

View File

@ -21,9 +21,6 @@
#pragma once
#include <kiwano/core/Resource.h>
#include <kiwano/base/ObjectBase.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
namespace kiwano
{
@ -36,7 +33,30 @@ KGE_DECLARE_SMART_PTR(Transcoder);
/**
* \addtogroup Audio
* @{
*/
/**
* \~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
@ -47,46 +67,41 @@ class KGE_API Transcoder : public ObjectBase
friend class AudioModule;
public:
/**
* \~chinese
* @brief
*/
struct Buffer
{
BYTE* data; ///< 音频数据
uint32_t size; ///< 音频数据大小
const WAVEFORMATEX* format; ///< 音频数据格式
};
Transcoder(const String& file_path);
Transcoder(const Resource& res);
Transcoder(const BinaryData& data, const AudioMetadata& metadata);
Transcoder();
~Transcoder();
virtual ~Transcoder();
/// \~chinese
/// @brief 获取数据缓冲
Buffer GetBuffer() const;
/// @brief 获取音频元数据
AudioMetadata GetMetadata() const;
/// \~chinese
/// @brief 清空数据缓冲
void ClearBuffer();
/// @brief 获取数据
BinaryData GetData() const;
/// \~chinese
/// @brief 清空数据
void Clear();
private:
/// \~chinese
/// @brief 解码本地音频文件
HRESULT LoadMediaFile(const String& file_path);
bool Load(const String& file_path);
/// \~chinese
/// @brief 解码音频资源
HRESULT LoadMediaResource(const Resource& res);
/// \~chinese
/// @brief 读取音频源数据
HRESULT ReadSource(IMFSourceReader* reader);
bool Load(const Resource& res);
bool Load(const BinaryData& data, const AudioMetadata& metadata);
private:
BYTE* wave_data_;
uint32_t wave_size_;
WAVEFORMATEX* wave_format_;
std::unique_ptr<uint8_t[]> raw_;
BinaryData data_;
AudioMetadata metadata_;
};
/** @} */