From a924eed28002472575c708a6644a8bf27ab98477 Mon Sep 17 00:00:00 2001 From: Nomango <569629550@qq.com> Date: Mon, 15 Oct 2018 20:16:08 +0800 Subject: [PATCH] add: decode media resources with Media Foundation and playback with XAudio2. --- core/e2dmacros.h | 14 +- core/e2dmodule.h | 29 +- core/e2dtool.h | 111 +-- core/impl/MediaAsyncCallback.cpp | 116 --- core/modules/Audio.cpp | 116 +-- core/tools/Music.cpp | 1214 +++++++++---------------- core/tools/Player.cpp | 10 +- project/vs2012/Easy2D.vcxproj | 1 - project/vs2012/Easy2D.vcxproj.filters | 3 - project/vs2013/Easy2D.vcxproj | 1 - project/vs2013/Easy2D.vcxproj.filters | 3 - project/vs2017/Easy2D.vcxproj | 3 +- project/vs2017/Easy2D.vcxproj.filters | 3 - 13 files changed, 481 insertions(+), 1143 deletions(-) delete mode 100644 core/impl/MediaAsyncCallback.cpp diff --git a/core/e2dmacros.h b/core/e2dmacros.h index b4d74380..3be7ea86 100644 --- a/core/e2dmacros.h +++ b/core/e2dmacros.h @@ -57,12 +57,11 @@ #include #include #include -#include +#include #include #include -#include -#include -#include +#include +#include // C++ RunTime Header Files #include @@ -83,11 +82,10 @@ #pragma comment(lib, "windowscodecs.lib") #pragma comment(lib, "winmm.lib") #pragma comment(lib, "dinput8.lib") -#pragma comment(lib, "shlwapi") -#pragma comment(lib, "mfplay.lib") -#pragma comment(lib, "mf.lib") +#pragma comment(lib, "xaudio2.lib") #pragma comment(lib, "mfplat.lib") -#pragma comment(lib, "mfuuid.lib") +#pragma comment(lib, "mfreadwrite.lib") +#pragma comment(lib, "shlwapi.lib") #ifndef HINST_THISCOMPONENT diff --git a/core/e2dmodule.h b/core/e2dmodule.h index 9f54a20e..505cbb7d 100644 --- a/core/e2dmodule.h +++ b/core/e2dmodule.h @@ -149,30 +149,21 @@ namespace e2d ~Audio(); - // 获取音量 - float GetVolume(); + // 开启设备 + void Open(); - // 设置音量 - void SetVolume( - float volume - ); + // 关闭设备 + void Close(); - // 是否静音 - bool GetMute(); - - // 设置静音 - void SetMute( - bool mute + // 创建音源 + HRESULT CreateVoice( + IXAudio2SourceVoice ** voice, + WAVEFORMATEX * wfx ); protected: - LPWSTR device_id; // Device ID. - IMMDeviceEnumerator *enum_; // Audio device enumerator. - IMMDeviceCollection *devices_; // Audio device collection. - IMMDevice *device_; // An audio device. - IMFAttributes *attributes_; // Attribute store. - IMFMediaSink *sink_; // Streaming audio renderer (SAR) - IMFSimpleAudioVolume *audio_volume; + IXAudio2 * x_audio2_; + IXAudio2MasteringVoice* mastering_voice_; }; diff --git a/core/e2dtool.h b/core/e2dtool.h index d1e8fb16..11d6d0bf 100644 --- a/core/e2dtool.h +++ b/core/e2dtool.h @@ -71,19 +71,10 @@ namespace e2d // 音乐 class Music - : public IMFAsyncCallback { public: Music(); - explicit Music( - const e2d::String& file_path /* 音乐文件路径 */ - ); - - explicit Music( - const Resource& res /* 音乐资源 */ - ); - virtual ~Music(); // 打开音乐文件 @@ -104,6 +95,9 @@ namespace e2d // 暂停 void Pause(); + // 继续 + void Resume(); + // 停止 void Stop(); @@ -113,99 +107,26 @@ namespace e2d // 是否正在播放 bool IsPlaying() const; - // 设置音量 - bool SetVolume( - float volume /* 范围: 0.0 ~ 1.0 */ - ); - // 获取音量 float GetVolume() const; - // IUnknown methods - STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override; - STDMETHODIMP_(ULONG) AddRef() override; - STDMETHODIMP_(ULONG) Release() override; + // 设置音量 + bool SetVolume( + float volume /* 1.0 为原始音量 */ + ); - // IMFAsyncCallback methods - STDMETHODIMP GetParameters(DWORD*, DWORD*) override; - STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult) override; + // 获取 IXAudio2SourceVoice 对象 + IXAudio2SourceVoice * GetSourceVoice() const; protected: - enum class State : int - { - Closed = 0, - Loaded, - Started, - Paused, - Stopped, - Closing - }; - E2D_DISABLE_COPY(Music); - // Media event handlers - HRESULT OnNewPresentation(IMFMediaEvent *pEvent); - - HRESULT StartPlayback(); - HRESULT CreateMediaSource(PCWSTR sURL, IMFMediaSource **ppSource); - HRESULT HandleEvent(UINT_PTR pUnkPtr); - - // Add a source node to a topology. - HRESULT AddSourceNode( - IMFTopology *pTopology, // Topology. - IMFMediaSource *pSource, // Media source. - IMFPresentationDescriptor *pPD, // Presentation descriptor. - IMFStreamDescriptor *pSD, // Stream descriptor. - IMFTopologyNode **ppNode // Receives the node pointer. - ); - - // Add an output node to a topology. - HRESULT AddOutputNode( - IMFTopology *pTopology, // Topology. - IMFActivate *pActivate, // Media sink activation object. - DWORD dwId, // Identifier of the stream sink. - IMFTopologyNode **ppNode // Receives the node pointer. - ); - - HRESULT AddBranchToPartialTopology( - IMFTopology *pTopology, // Topology. - IMFMediaSource *pSource, // Media source. - IMFPresentationDescriptor *pPD, // Presentation descriptor. - DWORD iStream // Stream index. - ); - - // Create a playback topology from a media source. - HRESULT CreatePlaybackTopology( - IMFMediaSource *pSource, // Media source. - IMFPresentationDescriptor *pPD, // Presentation descriptor. - IMFTopology **ppTopology // Receives a pointer to the topology. - ); - - // Create an activation object for a renderer, based on the stream media type. - HRESULT CreateMediaSinkActivate( - IMFStreamDescriptor *pSourceSD, // Pointer to the stream descriptor. - IMFActivate **ppActivate - ); - - HRESULT GetSimpleAudioVolume( - IMFSimpleAudioVolume** ppAudioVolume - ) const; - - static LRESULT CALLBACK MediaProc( - HWND hWnd, - UINT Msg, - WPARAM wParam, - LPARAM lParam - ); - protected: - long m_nRefCount; - int m_nTimes; - IMFMediaSession * m_pSession; - IMFMediaSource *m_pSource; - HWND m_hwndEvent; - State m_state; - HANDLE m_hCloseEvent; + bool opened_; + bool playing_; + UINT32 size_; + BYTE* wave_data_; + IXAudio2SourceVoice* voice_; }; @@ -280,11 +201,11 @@ namespace e2d ); // 获取音量 - float GetVolume(); + float GetVolume() const; // 设置音量 void SetVolume( - float volume /* 范围: 0.0 ~ 1.0 */ + float volume /* 1.0 为原始音量 */ ); // 暂停所有音乐 diff --git a/core/impl/MediaAsyncCallback.cpp b/core/impl/MediaAsyncCallback.cpp deleted file mode 100644 index ea81eb72..00000000 --- a/core/impl/MediaAsyncCallback.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2016-2018 Easy2D - 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 "..\e2dimpl.h" - -e2d::MediaAsyncCallback::MediaAsyncCallback(HWND hwnd, IMFMediaSession * pSession, HANDLE hCloseEvent) - : m_pSession(pSession) - , m_hCloseEvent(hCloseEvent) - , m_hwnd(hwnd) -{ -} - -HRESULT e2d::MediaAsyncCallback::QueryInterface(REFIID riid, void** ppv) -{ - static const QITAB qit[] = - { - QITABENT(MediaAsyncCallback, IMFAsyncCallback), - { 0 } - }; - return QISearch(this, qit, riid, ppv); -} - -ULONG e2d::MediaAsyncCallback::AddRef() -{ - return InterlockedIncrement(&m_nRefCount); -} - -ULONG e2d::MediaAsyncCallback::Release() -{ - ULONG uCount = InterlockedDecrement(&m_nRefCount); - if (uCount == 0) - { - delete this; - } - return uCount; -} - -HRESULT e2d::MediaAsyncCallback::GetParameters(DWORD *, DWORD *) -{ - // Implementation of this method is optional. - return E_NOTIMPL; -} - -HRESULT e2d::MediaAsyncCallback::Invoke(IMFAsyncResult *pResult) -{ - MediaEventType meType = MEUnknown; // Event type - - IMFMediaEvent *pEvent = NULL; - - // Get the event from the event queue. - HRESULT hr = m_pSession->EndGetEvent(pResult, &pEvent); - if (FAILED(hr)) - { - goto done; - } - - // Get the event type. - hr = pEvent->GetType(&meType); - if (FAILED(hr)) - { - goto done; - } - - if (meType == MESessionClosed) - { - // The session was closed. - // The application is waiting on the m_hCloseEvent event handle. - SetEvent(m_hCloseEvent); - } - else - { - // For all other events, get the next event in the queue. - hr = m_pSession->BeginGetEvent(this, NULL); - if (FAILED(hr)) - { - goto done; - } - } - - // Check the application state. - - // If a call to IMFMediaSession::Close is pending, it means the - // application is waiting on the m_hCloseEvent event and - // the application's message loop is blocked. - - // Otherwise, post a private window message to the application. - - //if (m_state != Closing) - { - // Leave a reference count on the event. - pEvent->AddRef(); - - ::PostMessage(m_hwnd, WM_APP + 1, (WPARAM)pEvent, (LPARAM)meType); - } - -done: - SafeRelease(pEvent); - return S_OK; -} diff --git a/core/modules/Audio.cpp b/core/modules/Audio.cpp index aaafdbe5..14e26924 100644 --- a/core/modules/Audio.cpp +++ b/core/modules/Audio.cpp @@ -22,128 +22,46 @@ e2d::Audio::Audio() - : enum_(nullptr) - , devices_(nullptr) - , device_(nullptr) - , attributes_(nullptr) - , sink_(nullptr) - , device_id(nullptr) - , audio_volume(nullptr) + : x_audio2_(nullptr) + , mastering_voice_(nullptr) { ThrowIfFailed( MFStartup(MF_VERSION) ); - // Create the device enumerator. ThrowIfFailed( - CoCreateInstance( - __uuidof(MMDeviceEnumerator), - NULL, - CLSCTX_ALL, - __uuidof(IMMDeviceEnumerator), - (void**)&enum_ - ) - ); - - // Enumerate the rendering devices. - ThrowIfFailed( - enum_->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices_) - ); - - // Get ID of the first device in the list. - ThrowIfFailed( - devices_->Item(0, &device_) + XAudio2Create(&x_audio2_) ); ThrowIfFailed( - device_->GetId(&device_id) + x_audio2_->CreateMasteringVoice(&mastering_voice_) ); - - // Create an attribute store and set the device ID attribute. - ThrowIfFailed( - MFCreateAttributes(&attributes_, 2) - ); - - ThrowIfFailed( - attributes_->SetString( - MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, - device_id - ) - ); - - // Create the audio renderer. - ThrowIfFailed( - MFCreateAudioRenderer(attributes_, &sink_) - ); - - IMFGetService* service = NULL; - - ThrowIfFailed( - sink_->QueryInterface(IID_IMFGetService, (void **)&service) - ); - - ThrowIfFailed( - service->GetService(MR_POLICY_VOLUME_SERVICE, IID_PPV_ARGS(&audio_volume)) - ); - - SafeRelease(service); } e2d::Audio::~Audio() { - SafeRelease(enum_); - SafeRelease(devices_); - SafeRelease(device_); - SafeRelease(attributes_); - SafeRelease(audio_volume); - SafeRelease(sink_); - CoTaskMemFree(device_id); + if (mastering_voice_) + { + mastering_voice_->DestroyVoice(); + mastering_voice_ = nullptr; + } + + SafeRelease(x_audio2_); MFShutdown(); } -float e2d::Audio::GetVolume() +HRESULT e2d::Audio::CreateVoice(IXAudio2SourceVoice ** voice, WAVEFORMATEX * wfx) { - float volume = 0.f; - if (audio_volume) - { - HRESULT hr = audio_volume->GetMasterVolume(&volume); - if (SUCCEEDED(hr)) - { - return volume; - } - } - return 0.f; + return x_audio2_->CreateSourceVoice(voice, wfx, 0, XAUDIO2_DEFAULT_FREQ_RATIO); } -void e2d::Audio::SetVolume(float volume) +void e2d::Audio::Open() { - if (audio_volume) - { - volume = std::min(std::max(volume, 0.f), 1.f); - HRESULT hr = audio_volume->SetMasterVolume(volume); - printf("呵呵%#X\n", hr); - } + x_audio2_->StartEngine(); } -bool e2d::Audio::GetMute() +void e2d::Audio::Close() { - BOOL mute = FALSE; - if (audio_volume) - { - HRESULT hr = audio_volume->GetMute(&mute); - if (SUCCEEDED(hr)) - { - return mute ? true : false; - } - } - return FALSE; -} - -void e2d::Audio::SetMute(bool mute) -{ - if (audio_volume) - { - audio_volume->SetMute(mute ? TRUE : FALSE); - } + x_audio2_->StopEngine(); } diff --git a/core/tools/Music.cpp b/core/tools/Music.cpp index 74323d0a..58052b51 100644 --- a/core/tools/Music.cpp +++ b/core/tools/Music.cpp @@ -19,884 +19,522 @@ // THE SOFTWARE. #include "..\e2dtool.h" -#include - -#define MF_CLASS_NAME L"MediaFoundationCallbackWnd" -#define WM_APP_PLAYER_EVENT (WM_APP + 1) +#include "..\e2dmodule.h" -namespace +inline bool TraceError(wchar_t* prompt) { - HINSTANCE media_instance = nullptr; + WARN("Music error: %s failed!", prompt); + return false; +} - inline void Trace(wchar_t* msg) - { - WARN("Trace error: %s.", msg); - } +inline bool TraceError(wchar_t* prompt, HRESULT hr) +{ + WARN("Music error: %s (%#X)", prompt, hr); + return false; +} - inline void Trace(wchar_t* msg, HRESULT hr) + +namespace e2d +{ + + class Transcoder { - WARN("Trace error: %s (%#X).", msg, hr); - } + WAVEFORMATEX* wave_format_; + + public: + Transcoder() + : wave_format_(nullptr) + { + } + + ~Transcoder() + { + if (wave_format_) + { + ::CoTaskMemFree(wave_format_); + wave_format_ = nullptr; + } + } + + WAVEFORMATEX* GetWaveFormatEx() + { + return wave_format_; + } + + bool LoadMediaFile(LPCWSTR file_path, BYTE** wave_data, UINT32* wave_data_size) + { + HRESULT hr = S_OK; + + IMFSourceReader* reader = nullptr; + + hr = MFCreateSourceReaderFromURL( + file_path, + nullptr, + &reader + ); + + if (SUCCEEDED(hr)) + { + hr = ReadSource(reader, wave_data, wave_data_size); + } + + SafeRelease(reader); + + return SUCCEEDED(hr); + } + + bool LoadMediaResource(LPCWSTR res_name, LPCWSTR res_type, BYTE** wave_data, UINT32* wave_data_size) + { + HRESULT hr = S_OK; + HRSRC res_info; + HGLOBAL res_data; + DWORD res_size; + void* res; + + IStream* stream = nullptr; + IMFByteStream* byte_stream = nullptr; + IMFSourceReader* reader = nullptr; + + res_info = FindResourceW(HINST_THISCOMPONENT, res_name, res_type); + if (res_info == nullptr) + { + return TraceError(L"FindResource"); + } + + res_data = LoadResource(HINST_THISCOMPONENT, res_info); + if (res_data == nullptr) + { + return TraceError(L"LoadResource"); + } + + res_size = SizeofResource(HINST_THISCOMPONENT, res_info); + if (res_size == 0) + { + return TraceError(L"SizeofResource"); + } + + res = LockResource(res_data); + if (res == nullptr) + { + return TraceError(L"LockResource"); + } + + stream = ::SHCreateMemStream( + static_cast(res), + static_cast(res_size) + ); + + if (stream == nullptr) + { + return TraceError(L"SHCreateMemStream"); + } + + if (SUCCEEDED(hr)) + { + hr = MFCreateMFByteStreamOnStream(stream, &byte_stream); + } + + if (SUCCEEDED(hr)) + { + hr = MFCreateSourceReaderFromByteStream( + byte_stream, + nullptr, + &reader + ); + } + + if (SUCCEEDED(hr)) + { + hr = ReadSource(reader, wave_data, wave_data_size); + } + + SafeRelease(stream); + SafeRelease(byte_stream); + SafeRelease(reader); + + return SUCCEEDED(hr); + } + + HRESULT ReadSource(IMFSourceReader* reader, BYTE** wave_data, UINT32* wave_data_size) + { + HRESULT hr = S_OK; + DWORD max_stream_size = 0; + + IMFMediaType* partial_type = nullptr; + IMFMediaType* uncompressed_type = nullptr; + + hr = MFCreateMediaType(&partial_type); + + if (SUCCEEDED(hr)) + { + hr = partial_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + } + + if (SUCCEEDED(hr)) + { + hr = partial_type->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); + } + + // 设置 source reader 的媒体类型,它将使用合适的解码器去解码这个音频 + if (SUCCEEDED(hr)) + { + hr = reader->SetCurrentMediaType( + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + 0, + partial_type + ); + } + + // 从 IMFMediaType 中获取 WAVEFORMAT 结构 + if (SUCCEEDED(hr)) + { + hr = reader->GetCurrentMediaType( + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + &uncompressed_type + ); + } + + // 指定音频流 + if (SUCCEEDED(hr)) + { + hr = reader->SetStreamSelection( + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + true + ); + } + + // 获取 WAVEFORMAT 数据 + if (SUCCEEDED(hr)) + { + UINT32 size = 0; + hr = MFCreateWaveFormatExFromMFMediaType( + uncompressed_type, + &wave_format_, + &size + ); + } + + // 估算音频流大小 + if (SUCCEEDED(hr)) + { + PROPVARIANT prop; + PropVariantInit(&prop); + + hr = reader->GetPresentationAttribute( + MF_SOURCE_READER_MEDIASOURCE, + MF_PD_DURATION, + &prop + ); + + LONGLONG duration = prop.uhVal.QuadPart; + max_stream_size = static_cast( + (duration * wave_format_->nAvgBytesPerSec) / 10000000 + 1 + ); + PropVariantClear(&prop); + } + + // 读取音频数据 + if (SUCCEEDED(hr)) + { + DWORD flags = 0; + DWORD position = 0; + + IMFSample* sample = nullptr; + IMFMediaBuffer* buffer = nullptr; + BYTE* data = new (std::nothrow) BYTE[max_stream_size]; + + if (data == nullptr) + { + TraceError(L"Low memory"); + hr = E_OUTOFMEMORY; + } + else + { + while (true) + { + hr = reader->ReadSample( + MF_SOURCE_READER_FIRST_AUDIO_STREAM, + 0, + nullptr, + &flags, + nullptr, + &sample + ); + + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { break; } + + if (sample == nullptr) { continue; } + + if (SUCCEEDED(hr)) + { + hr = sample->ConvertToContiguousBuffer(&buffer); + + if (SUCCEEDED(hr)) + { + BYTE *audio_data = nullptr; + DWORD sample_buffer_length = 0; + + hr = buffer->Lock( + &audio_data, + nullptr, + &sample_buffer_length + ); + + if (SUCCEEDED(hr)) + { + for (DWORD i = 0; i < sample_buffer_length; i++) + { + data[position++] = audio_data[i]; + } + hr = buffer->Unlock(); + } + } + SafeRelease(buffer); + } + SafeRelease(sample); + + if (FAILED(hr)) { break; } + } + + if (SUCCEEDED(hr)) + { + *wave_data = data; + *wave_data_size = position; + } + } + } + + SafeRelease(partial_type); + SafeRelease(uncompressed_type); + + return hr; + } + }; + } e2d::Music::Music() - : m_pSession(nullptr) - , m_pSource(nullptr) - , m_hwndEvent(nullptr) - , m_state(State::Closed) - , m_hCloseEvent(nullptr) - , m_nTimes(0) + : opened_(false) + , playing_(false) + , wave_data_(nullptr) + , size_(0) + , voice_(nullptr) { - if (!media_instance) - { - media_instance = ::GetModuleHandle(NULL); - - WNDCLASS wc; - wc.style = 0; - wc.lpfnWndProc = Music::MediaProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = media_instance; - wc.hIcon = 0; - wc.hCursor = ::LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = NULL; - wc.lpszMenuName = NULL; - wc.lpszClassName = MF_CLASS_NAME; - - if (!::RegisterClass(&wc)) - { - return; - } - } - - m_hwndEvent = ::CreateWindowExW( - WS_EX_APPWINDOW, - MF_CLASS_NAME, - NULL, - WS_POPUPWINDOW, - 0, 0, 0, 0, - NULL, - NULL, - media_instance, - NULL - ); - - if (m_hwndEvent) - { - ::SetWindowLongW(m_hwndEvent, GWLP_USERDATA, (LONG_PTR)this); - } - - m_hCloseEvent = ::CreateEventW(NULL, FALSE, FALSE, NULL); - if (m_hCloseEvent == NULL) - { - ThrowIfFailed( - HRESULT_FROM_WIN32(GetLastError()) - ); - } -} - -e2d::Music::Music(const e2d::String & file_path) -{ - this->Load(file_path); -} - -e2d::Music::Music(const Resource& res) -{ - this->Load(res); } e2d::Music::~Music() { Close(); - - if (m_hCloseEvent) - { - ::CloseHandle(m_hCloseEvent); - m_hCloseEvent = NULL; - } - - DestroyWindow(m_hwndEvent); } bool e2d::Music::Load(const e2d::String & file_path) { - // 1. Create a new media session. - // 2. Create the media source. - // 3. Create the topology. - // 4. Queue the topology [asynchronous] - // 5. Start playback [asynchronous - does not happen in this method.] - - IMFTopology *pTopology = NULL; - IMFPresentationDescriptor* pSourcePD = NULL; - - // Close the old session, if any. - Close(); - - // Create the media session. - HRESULT hr = MFCreateMediaSession(NULL, &m_pSession); - if (FAILED(hr)) + if (opened_) { - goto done; + Close(); } - // Start pulling events from the media session - hr = m_pSession->BeginGetEvent((IMFAsyncCallback*)this, NULL); - if (FAILED(hr)) + File music_file; + if (!music_file.Open(file_path)) { - goto done; + WARN("Music::Load error: File not found."); + return false; } - // Create the media source. - hr = CreateMediaSource(static_cast(file_path), &m_pSource); - if (FAILED(hr)) + // 用户输入的路径不一定是完整路径,因为用户可能通过 File::AddSearchPath 添加 + // 默认搜索路径,所以需要通过 File::GetPath 获取完整路径 + String music_file_path = music_file.GetPath(); + + Transcoder transcoder; + if (!transcoder.LoadMediaFile((LPCWSTR)music_file_path, &wave_data_, &size_)) { - goto done; + return false; } - // Create the presentation descriptor for the media source. - hr = m_pSource->CreatePresentationDescriptor(&pSourcePD); + HRESULT hr = Device::GetAudio()->CreateVoice(&voice_, transcoder.GetWaveFormatEx()); if (FAILED(hr)) { - goto done; + if (wave_data_) + { + delete[] wave_data_; + wave_data_ = nullptr; + } + return TraceError(L"Create source voice error", hr); } - // Create a partial topology. - hr = CreatePlaybackTopology(m_pSource, pSourcePD, &pTopology); - if (FAILED(hr)) - { - goto done; - } - - // Set the topology on the media session. - hr = m_pSession->SetTopology(0, pTopology); - if (FAILED(hr)) - { - goto done; - } - - m_state = State::Loaded; - - // If SetTopology succeeds, the media session will queue an - // MESessionTopologySet event. - -done: - if (FAILED(hr)) - { - m_state = State::Closed; - } - - SafeRelease(pSourcePD); - SafeRelease(pTopology); - return hr; + opened_ = true; + return true; } bool e2d::Music::Load(const Resource& res) { - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - return false; + if (opened_) + { + Close(); + } + + Transcoder transcoder; + if (!transcoder.LoadMediaResource(MAKEINTRESOURCE(res.id), (LPCWSTR)res.type, &wave_data_, &size_)) + { + return false; + } + + HRESULT hr = Device::GetAudio()->CreateVoice(&voice_, transcoder.GetWaveFormatEx()); + if (FAILED(hr)) + { + if (wave_data_) + { + delete[] wave_data_; + wave_data_ = nullptr; + } + return TraceError(L"Create source voice error", hr); + } + + opened_ = true; + return true; } bool e2d::Music::Play(int loop_count) { - if (m_pSession == NULL || m_pSource == NULL) + if (!opened_) { + WARN("Music::Play Failed: Music must be opened first!"); return false; } - HRESULT hr = StartPlayback(); - if (SUCCEEDED(hr)) + + if (voice_ == nullptr) { - m_nTimes = loop_count; - return true; + WARN("Music::Play Failed: IXAudio2SourceVoice Null pointer exception!"); + return false; } - return false; + + XAUDIO2_VOICE_STATE state; + voice_->GetState(&state); + if (state.BuffersQueued) + { + Stop(); + } + + if (loop_count < 0) + { + loop_count = XAUDIO2_LOOP_INFINITE; + } + else + { + loop_count = std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1); + } + + // 提交 wave 样本数据 + XAUDIO2_BUFFER buffer = { 0 }; + buffer.pAudioData = wave_data_; + buffer.Flags = XAUDIO2_END_OF_STREAM; + buffer.AudioBytes = size_; + buffer.LoopCount = loop_count; + + HRESULT hr; + if (FAILED(hr = voice_->SubmitSourceBuffer(&buffer))) + { + return TraceError(L"Submitting source buffer error", hr); + } + + hr = voice_->Start(0); + + playing_ = SUCCEEDED(hr); + + return playing_; } void e2d::Music::Pause() { - if (m_state != State::Started) + if (voice_) { - return; - } - if (m_pSession == NULL || m_pSource == NULL) - { - return; + if (SUCCEEDED(voice_->Stop())) + { + playing_ = false; + } } +} - HRESULT hr = m_pSession->Pause(); - if (SUCCEEDED(hr)) +void e2d::Music::Resume() +{ + if (voice_) { - m_state = State::Paused; + if (SUCCEEDED(voice_->Start())) + { + playing_ = true; + } } } void e2d::Music::Stop() { - if (m_state != State::Started && m_state != State::Paused) + if (voice_) { - return; - } - if (m_pSession == NULL) - { - return; - } - - HRESULT hr = m_pSession->Stop(); - if (SUCCEEDED(hr)) - { - m_state = State::Stopped; + if (SUCCEEDED(voice_->Stop())) + { + voice_->ExitLoop(); + voice_->FlushSourceBuffers(); + playing_ = false; + } } } void e2d::Music::Close() { - // The IMFMediaSession::Close method is asynchronous, but the - // e2d::Music::CloseSession method waits on the MESessionClosed event. - // - // MESessionClosed is guaranteed to be the last event that the - // media session fires. - - HRESULT hr = S_OK; - - // First close the media session. - if (m_pSession) + if (voice_) { - DWORD dwWaitResult = 0; - - m_state = State::Closing; - - hr = m_pSession->Close(); - - // Wait for the close operation to complete - if (SUCCEEDED(hr)) - { - WaitForSingleObject(m_hCloseEvent, 5000); - } + voice_->Stop(); + voice_->FlushSourceBuffers(); + voice_->DestroyVoice(); + voice_ = nullptr; } - // Complete shutdown operations. - if (SUCCEEDED(hr)) + if (wave_data_) { - // Shut down the media source. (Synchronous operation, no events.) - if (m_pSource) - { - (void)m_pSource->Shutdown(); - } - // Shut down the media session. (Synchronous operation, no events.) - if (m_pSession) - { - (void)m_pSession->Shutdown(); - } + delete[] wave_data_; + wave_data_ = nullptr; } - SafeRelease(m_pSource); - SafeRelease(m_pSession); - - m_state = State::Closed; + opened_ = false; + playing_ = false; } bool e2d::Music::IsPlaying() const { - return m_state == State::Started; -} - -bool e2d::Music::SetVolume(float volume) -{ - if (!m_pSession) - return false; - - IMFSimpleAudioVolume* pAudioVolume = NULL; - HRESULT hr = GetSimpleAudioVolume(&pAudioVolume); - if (SUCCEEDED(hr)) + if (opened_ && voice_) { - volume = std::min(std::max(volume, 0.f), 1.f); - hr = pAudioVolume->SetMasterVolume(volume); + XAUDIO2_VOICE_STATE state; + voice_->GetState(&state); + if (state.BuffersQueued && playing_) + return true; } - - SafeRelease(pAudioVolume); - return SUCCEEDED(hr); + return false; } float e2d::Music::GetVolume() const { - if (!m_pSession) - return 0.f; - - float volume = 0.f; - IMFSimpleAudioVolume* pAudioVolume = NULL; - HRESULT hr = GetSimpleAudioVolume(&pAudioVolume); - if (SUCCEEDED(hr)) - { - hr = pAudioVolume->GetMasterVolume(&volume); - } - SafeRelease(pAudioVolume); - - if (SUCCEEDED(hr)) + if (voice_) { + float volume = 0.f; + voice_->GetVolume(&volume); return volume; } return 0.f; } -HRESULT e2d::Music::QueryInterface(REFIID riid, void** ppv) +bool e2d::Music::SetVolume(float volume) { - static const QITAB qit[] = + if (voice_) { - QITABENT(Music, IMFAsyncCallback), - { 0 } - }; - return QISearch(this, qit, riid, ppv); + volume = std::min(std::max(volume, -224.f), 224.f); + return SUCCEEDED(voice_->SetVolume(volume)); + } + return false; } -ULONG e2d::Music::AddRef() +IXAudio2SourceVoice * e2d::Music::GetSourceVoice() const { - return InterlockedIncrement(&m_nRefCount); -} - -ULONG e2d::Music::Release() -{ - ULONG uCount = InterlockedDecrement(&m_nRefCount); - if (uCount == 0) - { - delete this; - } - return uCount; -} - -HRESULT e2d::Music::GetParameters(DWORD *, DWORD *) -{ - // Implementation of this method is optional. - return E_NOTIMPL; -} - -HRESULT e2d::Music::Invoke(IMFAsyncResult *pResult) -{ - MediaEventType meType = MEUnknown; // Event type - - IMFMediaEvent *pEvent = NULL; - - // Get the event from the event queue. - HRESULT hr = m_pSession->EndGetEvent(pResult, &pEvent); - if (FAILED(hr)) - { - goto done; - } - - // Get the event type. - hr = pEvent->GetType(&meType); - if (FAILED(hr)) - { - goto done; - } - - if (meType == MESessionClosed) - { - // The session was closed. - // The application is waiting on the m_hCloseEvent event handle. - SetEvent(m_hCloseEvent); - } - else - { - // For all other events, get the next event in the queue. - hr = m_pSession->BeginGetEvent(this, NULL); - if (FAILED(hr)) - { - goto done; - } - } - - // Check the application state. - - // If a call to IMFMediaSession::Close is pending, it means the - // application is waiting on the m_hCloseEvent event and - // the application's message loop is blocked. - - // Otherwise, post a private window message to the application. - - if (m_state != State::Closing) - { - // Leave a reference count on the event. - pEvent->AddRef(); - - ::PostMessage(m_hwndEvent, WM_APP_PLAYER_EVENT, (WPARAM)pEvent, (LPARAM)meType); - } - -done: - SafeRelease(pEvent); - return S_OK; -} - -HRESULT e2d::Music::StartPlayback() -{ - PROPVARIANT varStart; - PropVariantInit(&varStart); - - HRESULT hr = m_pSession->Start(&GUID_NULL, &varStart); - if (SUCCEEDED(hr)) - { - // Note: Start is an asynchronous operation. However, we - // can treat our state as being already started. If Start - // fails later, we'll get an MESessionStarted event with - // an error code, and we will update our state then. - m_state = State::Started; - } - PropVariantClear(&varStart); - return hr; -} - -HRESULT e2d::Music::HandleEvent(UINT_PTR pEventPtr) -{ - HRESULT hrStatus = S_OK; - MediaEventType meType = MEUnknown; - - IMFMediaEvent *pEvent = (IMFMediaEvent*)pEventPtr; - - if (pEvent == NULL) - { - return E_POINTER; - } - - // Get the event type. - HRESULT hr = pEvent->GetType(&meType); - if (FAILED(hr)) - { - goto done; - } - - // Get the event status. If the operation that triggered the event - // did not succeed, the status is a failure code. - hr = pEvent->GetStatus(&hrStatus); - - // Check if the async operation succeeded. - if (SUCCEEDED(hr) && FAILED(hrStatus)) - { - hr = hrStatus; - } - - if (FAILED(hr)) - { - goto done; - } - - switch (meType) - { - case MEEndOfPresentation: - printf("%d\n", m_nTimes); - if (m_nTimes) - { - --m_nTimes; - hr = StartPlayback(); - } - else - { - m_state = State::Stopped; - hr = S_OK; - } - break; - - case MENewPresentation: - hr = OnNewPresentation(pEvent); - break; - - default: - break; - } - -done: - SafeRelease(pEvent); - return hr; -} - -HRESULT e2d::Music::OnNewPresentation(IMFMediaEvent *pEvent) -{ - IMFPresentationDescriptor *pPD = NULL; - IMFTopology *pTopology = NULL; - - // Get the presentation descriptor from the event. - PROPVARIANT var; - HRESULT hr = pEvent->GetValue(&var); - if (FAILED(hr)) - { - goto done; - } - - if (var.vt == VT_UNKNOWN) - { - hr = var.punkVal->QueryInterface(&pPD); - } - else - { - hr = MF_E_INVALIDTYPE; - } - PropVariantClear(&var); - - if (FAILED(hr)) - { - goto done; - } - - // Create a partial topology. - hr = CreatePlaybackTopology(m_pSource, pPD, &pTopology); - if (FAILED(hr)) - { - goto done; - } - - // Set the topology on the media session. - hr = m_pSession->SetTopology(0, pTopology); - if (FAILED(hr)) - { - goto done; - } - - m_state = State::Loaded; - -done: - SafeRelease(pTopology); - SafeRelease(pPD); - return S_OK; -} - -HRESULT e2d::Music::GetSimpleAudioVolume(IMFSimpleAudioVolume ** ppAudioVolume) const -{ - if (ppAudioVolume == NULL) - return E_POINTER; - - IMFGetService* pGetService = NULL; - - HRESULT hr = m_pSession->QueryInterface(IID_IMFGetService, (void **)&pGetService); - if (SUCCEEDED(hr)) - { - hr = pGetService->GetService(MR_CAPTURE_POLICY_VOLUME_SERVICE, IID_PPV_ARGS(ppAudioVolume)); - } - SafeRelease(pGetService); - return hr; -} - -HRESULT e2d::Music::CreateMediaSource(PCWSTR sURL, IMFMediaSource **ppSource) -{ - MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID; - - IMFSourceResolver* pSourceResolver = NULL; - IUnknown* pSource = NULL; - - // Create the source resolver. - HRESULT hr = MFCreateSourceResolver(&pSourceResolver); - if (FAILED(hr)) - { - goto done; - } - - // Use the source resolver to create the media source. - - // Note: For simplicity this sample uses the synchronous method to create - // the media source. However, creating a media source can take a noticeable - // amount of time, especially for a network source. For a more responsive - // UI, use the asynchronous BeginCreateObjectFromURL method. - - hr = pSourceResolver->CreateObjectFromURL( - sURL, // URL of the source. - MF_RESOLUTION_MEDIASOURCE, // Create a source object. - NULL, // Optional property store. - &ObjectType, // Receives the created object type. - &pSource // Receives a pointer to the media source. - ); - if (FAILED(hr)) - { - goto done; - } - - // Get the IMFMediaSource interface from the media source. - hr = pSource->QueryInterface(IID_PPV_ARGS(ppSource)); - -done: - SafeRelease(pSourceResolver); - SafeRelease(pSource); - return hr; -} - -HRESULT e2d::Music::CreateMediaSinkActivate( - IMFStreamDescriptor *pSourceSD, - IMFActivate **ppActivate) -{ - IMFMediaTypeHandler *pHandler = NULL; - IMFActivate *pActivate = NULL; - - // Get the media type handler for the stream. - HRESULT hr = pSourceSD->GetMediaTypeHandler(&pHandler); - if (FAILED(hr)) - { - goto done; - } - - // Get the major media type. - GUID guidMajorType; - hr = pHandler->GetMajorType(&guidMajorType); - if (FAILED(hr)) - { - goto done; - } - - // Create an IMFActivate object for the renderer, based on the media type. - if (MFMediaType_Audio == guidMajorType) - { - // Create the audio renderer. - hr = MFCreateAudioRendererActivate(&pActivate); - } - else if (MFMediaType_Video == guidMajorType) - { - // Create the video renderer. - hr = MFCreateVideoRendererActivate(m_hwndEvent, &pActivate); - } - else - { - // Unknown stream type. - hr = E_FAIL; - // Optionally, you could deselect this stream instead of failing. - } - if (FAILED(hr)) - { - goto done; - } - - // Return IMFActivate pointer to caller. - *ppActivate = pActivate; - (*ppActivate)->AddRef(); - -done: - SafeRelease(pHandler); - SafeRelease(pActivate); - return hr; -} - -HRESULT e2d::Music::AddSourceNode( - IMFTopology *pTopology, - IMFMediaSource *pSource, - IMFPresentationDescriptor *pPD, - IMFStreamDescriptor *pSD, - IMFTopologyNode **ppNode) -{ - IMFTopologyNode *pNode = NULL; - - // Create the node. - HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &pNode); - if (FAILED(hr)) - { - goto done; - } - - // Set the attributes. - hr = pNode->SetUnknown(MF_TOPONODE_SOURCE, pSource); - if (FAILED(hr)) - { - goto done; - } - - hr = pNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD); - if (FAILED(hr)) - { - goto done; - } - - hr = pNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD); - if (FAILED(hr)) - { - goto done; - } - - // Add the node to the topology. - hr = pTopology->AddNode(pNode); - if (FAILED(hr)) - { - goto done; - } - - // Return the pointer to the caller. - *ppNode = pNode; - (*ppNode)->AddRef(); - -done: - SafeRelease(pNode); - return hr; -} - -HRESULT e2d::Music::AddOutputNode( - IMFTopology *pTopology, - IMFActivate *pActivate, - DWORD dwId, - IMFTopologyNode **ppNode) -{ - IMFTopologyNode *pNode = NULL; - - // Create the node. - HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &pNode); - if (FAILED(hr)) - { - goto done; - } - - // Set the object pointer. - hr = pNode->SetObject(pActivate); - if (FAILED(hr)) - { - goto done; - } - - // Set the stream sink ID attribute. - hr = pNode->SetUINT32(MF_TOPONODE_STREAMID, dwId); - if (FAILED(hr)) - { - goto done; - } - - hr = pNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE); - if (FAILED(hr)) - { - goto done; - } - - // Add the node to the topology. - hr = pTopology->AddNode(pNode); - if (FAILED(hr)) - { - goto done; - } - - // Return the pointer to the caller. - *ppNode = pNode; - (*ppNode)->AddRef(); - -done: - SafeRelease(pNode); - return hr; -} - -HRESULT e2d::Music::AddBranchToPartialTopology( - IMFTopology *pTopology, - IMFMediaSource *pSource, - IMFPresentationDescriptor *pPD, - DWORD iStream) -{ - // Add a topology branch for one stream. - // - // For each stream, this function does the following: - // - // 1. Creates a source node associated with the stream. - // 2. Creates an output node for the renderer. - // 3. Connects the two nodes. - // - // The media session will add any decoders that are needed. - - IMFStreamDescriptor *pSD = NULL; - IMFActivate *pSinkActivate = NULL; - IMFTopologyNode *pSourceNode = NULL; - IMFTopologyNode *pOutputNode = NULL; - - BOOL fSelected = FALSE; - - HRESULT hr = pPD->GetStreamDescriptorByIndex(iStream, &fSelected, &pSD); - if (FAILED(hr)) - { - goto done; - } - - if (fSelected) - { - // Create the media sink activation object. - hr = CreateMediaSinkActivate(pSD, &pSinkActivate); - if (FAILED(hr)) - { - goto done; - } - - // Add a source node for this stream. - hr = AddSourceNode(pTopology, pSource, pPD, pSD, &pSourceNode); - if (FAILED(hr)) - { - goto done; - } - - // Create the output node for the renderer. - hr = AddOutputNode(pTopology, pSinkActivate, 0, &pOutputNode); - if (FAILED(hr)) - { - goto done; - } - - // Connect the source node to the output node. - hr = pSourceNode->ConnectOutput(0, pOutputNode, 0); - } - // else: If not selected, don't add the branch. - -done: - SafeRelease(pSD); - SafeRelease(pSinkActivate); - SafeRelease(pSourceNode); - SafeRelease(pOutputNode); - return hr; -} - -HRESULT e2d::Music::CreatePlaybackTopology( - IMFMediaSource *pSource, - IMFPresentationDescriptor *pPD, - IMFTopology **ppTopology) -{ - IMFTopology *pTopology = NULL; - DWORD cSourceStreams = 0; - - // Create a new topology. - HRESULT hr = MFCreateTopology(&pTopology); - if (FAILED(hr)) - { - goto done; - } - - // Get the number of streams in the media source. - hr = pPD->GetStreamDescriptorCount(&cSourceStreams); - if (FAILED(hr)) - { - goto done; - } - - // For each stream, create the topology nodes and add them to the topology. - for (DWORD i = 0; i < cSourceStreams; i++) - { - hr = AddBranchToPartialTopology(pTopology, pSource, pPD, i); - if (FAILED(hr)) - { - goto done; - } - } - - // Return the IMFTopology pointer to the caller. - *ppTopology = pTopology; - (*ppTopology)->AddRef(); - -done: - SafeRelease(pTopology); - return hr; -} - -LRESULT e2d::Music::MediaProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -{ - switch (Msg) - { - case WM_APP_PLAYER_EVENT: - { - Music * music = reinterpret_cast( - static_cast(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)) - ); - if (music) - { - music->HandleEvent(wParam); - } - } - break; - - default: - return ::DefWindowProc(hWnd, Msg, wParam, lParam); - } - return 0; + return voice_; } diff --git a/core/tools/Player.cpp b/core/tools/Player.cpp index 31f778e9..9a83b101 100644 --- a/core/tools/Player.cpp +++ b/core/tools/Player.cpp @@ -68,7 +68,7 @@ void e2d::Player::Resume(const String & file_path) size_t hash = file_path.GetHash(); if (musics_.end() != musics_.find(hash)) - musics_[hash]->Play(); + musics_[hash]->Resume(); } void e2d::Player::Stop(const String & file_path) @@ -137,7 +137,7 @@ void e2d::Player::Pause(const Resource& res) void e2d::Player::Resume(const Resource& res) { if (musics_.end() != musics_.find(res.id)) - musics_[res.id]->Play(); + musics_[res.id]->Resume(); } void e2d::Player::Stop(const Resource& res) @@ -153,14 +153,14 @@ bool e2d::Player::IsPlaying(const Resource& res) return false; } -float e2d::Player::GetVolume() +float e2d::Player::GetVolume() const { return volume_; } void e2d::Player::SetVolume(float volume) { - volume_ = std::min(std::max(volume, 0.f), 1.f); + volume_ = std::min(std::max(volume, -224.f), 224.f); for (const auto& pair : musics_) { pair.second->SetVolume(volume_); @@ -179,7 +179,7 @@ void e2d::Player::ResumeAll() { for (const auto& pair : musics_) { - pair.second->Play(); + pair.second->Resume(); } } diff --git a/project/vs2012/Easy2D.vcxproj b/project/vs2012/Easy2D.vcxproj index c7b5f618..341c8e0d 100644 --- a/project/vs2012/Easy2D.vcxproj +++ b/project/vs2012/Easy2D.vcxproj @@ -45,7 +45,6 @@ - diff --git a/project/vs2012/Easy2D.vcxproj.filters b/project/vs2012/Easy2D.vcxproj.filters index f1ef1fdf..20bd63df 100644 --- a/project/vs2012/Easy2D.vcxproj.filters +++ b/project/vs2012/Easy2D.vcxproj.filters @@ -141,9 +141,6 @@ impl - - impl - events diff --git a/project/vs2013/Easy2D.vcxproj b/project/vs2013/Easy2D.vcxproj index f1c46bcb..a8b6502b 100644 --- a/project/vs2013/Easy2D.vcxproj +++ b/project/vs2013/Easy2D.vcxproj @@ -189,7 +189,6 @@ - diff --git a/project/vs2013/Easy2D.vcxproj.filters b/project/vs2013/Easy2D.vcxproj.filters index f1ef1fdf..20bd63df 100644 --- a/project/vs2013/Easy2D.vcxproj.filters +++ b/project/vs2013/Easy2D.vcxproj.filters @@ -141,9 +141,6 @@ impl - - impl - events diff --git a/project/vs2017/Easy2D.vcxproj b/project/vs2017/Easy2D.vcxproj index 74d87125..6fedf7cd 100644 --- a/project/vs2017/Easy2D.vcxproj +++ b/project/vs2017/Easy2D.vcxproj @@ -101,7 +101,7 @@ Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) false - None + EditAndContinue false true @@ -222,7 +222,6 @@ - diff --git a/project/vs2017/Easy2D.vcxproj.filters b/project/vs2017/Easy2D.vcxproj.filters index 507d84d1..20bd63df 100644 --- a/project/vs2017/Easy2D.vcxproj.filters +++ b/project/vs2017/Easy2D.vcxproj.filters @@ -216,9 +216,6 @@ modules - - impl -