2023-03-25 08:37:51 +08:00
|
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
// File: MfMediaEngine.cpp
|
|
|
|
//
|
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
// Licensed under the MIT License.
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
|
2023-04-02 01:51:36 +08:00
|
|
|
#include "media/MfMediaEngine.h"
|
2023-03-25 08:37:51 +08:00
|
|
|
|
2023-04-02 01:51:36 +08:00
|
|
|
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP || defined(AXME_USE_IMFME))
|
2023-03-25 08:37:51 +08:00
|
|
|
|
|
|
|
# include "ntcvt/ntcvt.hpp"
|
|
|
|
|
|
|
|
# include "MFUtils.h"
|
|
|
|
|
2023-04-02 01:51:36 +08:00
|
|
|
# include "yasio/stl/string_view.hpp"
|
|
|
|
|
2023-03-25 08:37:51 +08:00
|
|
|
NS_AX_BEGIN
|
|
|
|
|
|
|
|
using Microsoft::WRL::ComPtr;
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
class MediaEngineNotify : public IMFMediaEngineNotify
|
|
|
|
{
|
|
|
|
long m_cRef;
|
|
|
|
IMFNotify* m_pCB;
|
|
|
|
|
|
|
|
public:
|
|
|
|
MediaEngineNotify() noexcept : m_cRef(1), m_pCB(nullptr) {}
|
|
|
|
|
|
|
|
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
|
|
|
|
{
|
|
|
|
if (__uuidof(IMFMediaEngineNotify) == riid)
|
|
|
|
{
|
|
|
|
*ppv = static_cast<IMFMediaEngineNotify*>(this);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*ppv = nullptr;
|
|
|
|
return E_NOINTERFACE;
|
|
|
|
}
|
|
|
|
|
|
|
|
AddRef();
|
|
|
|
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); }
|
|
|
|
|
|
|
|
STDMETHODIMP_(ULONG) Release()
|
|
|
|
{
|
|
|
|
LONG cRef = InterlockedDecrement(&m_cRef);
|
|
|
|
if (cRef == 0)
|
|
|
|
{
|
|
|
|
delete this;
|
|
|
|
}
|
|
|
|
return cRef;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetCallback(IMFNotify* pCB) { m_pCB = pCB; }
|
|
|
|
|
|
|
|
// EventNotify is called when the Media Engine sends an event.
|
|
|
|
STDMETHODIMP EventNotify(DWORD meEvent, DWORD_PTR param1, DWORD)
|
|
|
|
{
|
|
|
|
if (meEvent == MF_MEDIA_ENGINE_EVENT_NOTIFYSTABLESTATE)
|
|
|
|
{
|
|
|
|
SetEvent(reinterpret_cast<HANDLE>(param1));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_pCB->OnMediaEngineEvent(meEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
MfMediaEngine::MfMediaEngine() noexcept : m_bkgColor{} {}
|
|
|
|
|
|
|
|
MfMediaEngine::~MfMediaEngine()
|
|
|
|
{
|
|
|
|
if (m_mediaEngine)
|
|
|
|
m_mediaEngine->Shutdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::Initialize()
|
|
|
|
{
|
|
|
|
// Initialize Media Foundation (see Main.cpp for code to handle Windows N Editions)
|
|
|
|
DX::ThrowIfFailed(MFUtils::InitializeMFOnce());
|
|
|
|
|
|
|
|
// Setup Media Engine
|
|
|
|
|
|
|
|
// Create our event callback object.
|
|
|
|
ComPtr<MediaEngineNotify> spNotify = new MediaEngineNotify();
|
|
|
|
if (spNotify == nullptr)
|
|
|
|
{
|
|
|
|
DX::ThrowIfFailed(E_OUTOFMEMORY);
|
|
|
|
}
|
|
|
|
|
|
|
|
spNotify->SetCallback(this);
|
|
|
|
|
|
|
|
// Set configuration attribiutes.
|
|
|
|
ComPtr<IMFAttributes> attributes;
|
|
|
|
DX::ThrowIfFailed(MFCreateAttributes(attributes.GetAddressOf(), 1));
|
|
|
|
// DX::ThrowIfFailed(attributes->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER,
|
|
|
|
// reinterpret_cast<IUnknown*>(dxgiManager.Get())));
|
|
|
|
DX::ThrowIfFailed(attributes->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, reinterpret_cast<IUnknown*>(spNotify.Get())));
|
|
|
|
DX::ThrowIfFailed(
|
2023-04-02 01:51:36 +08:00
|
|
|
attributes->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM));
|
|
|
|
|
|
|
|
DX::ThrowIfFailed(attributes->SetUINT32(MF_MEDIA_ENGINE_STREAM_CONTAINS_ALPHA_CHANNEL, 1));
|
2023-03-25 08:37:51 +08:00
|
|
|
|
|
|
|
// Create MediaEngine.
|
|
|
|
ComPtr<IMFMediaEngineClassFactory> mfFactory;
|
|
|
|
DX::ThrowIfFailed(
|
|
|
|
CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_ALL, IID_PPV_ARGS(mfFactory.GetAddressOf())));
|
|
|
|
|
|
|
|
DX::ThrowIfFailed(mfFactory->CreateInstance(0, attributes.Get(), m_mediaEngine.ReleaseAndGetAddressOf()));
|
|
|
|
|
2023-04-02 01:51:36 +08:00
|
|
|
MFUtils::CreateInstance(CLSID_WICImagingFactory, m_wicFactory);
|
2023-03-25 08:37:51 +08:00
|
|
|
|
|
|
|
return m_mediaEngine != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::SetLoop(bool bLoop)
|
|
|
|
{
|
|
|
|
if (m_mediaEngine)
|
|
|
|
return SUCCEEDED(m_mediaEngine->SetLoop(bLoop));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::SetRate(double fRate)
|
|
|
|
{
|
|
|
|
if (m_mediaEngine)
|
|
|
|
return SUCCEEDED(m_mediaEngine->SetPlaybackRate(fRate));
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::Play()
|
|
|
|
{
|
|
|
|
if (m_state == MEMediaState::Playing)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_mediaEngine)
|
|
|
|
{
|
|
|
|
if (m_state == MEMediaState::Stopped)
|
|
|
|
m_mediaEngine->SetCurrentTime(0);
|
|
|
|
hr = (m_mediaEngine->Play());
|
|
|
|
m_state = MEMediaState::Playing;
|
|
|
|
}
|
|
|
|
return SUCCEEDED(hr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::Pause()
|
|
|
|
{
|
|
|
|
if (m_mediaEngine)
|
|
|
|
return SUCCEEDED(m_mediaEngine->Pause());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MfMediaEngine::SetMuted(bool muted)
|
|
|
|
{
|
|
|
|
if (m_mediaEngine)
|
|
|
|
{
|
|
|
|
DX::ThrowIfFailed(m_mediaEngine->SetMuted(muted));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::Open(std::string_view sourceUri)
|
|
|
|
{
|
2023-04-02 01:51:36 +08:00
|
|
|
if (cxx20::starts_with(sourceUri, FILE_URL_SCHEME))
|
|
|
|
sourceUri.remove_prefix(FILE_URL_SCHEME.length());
|
2023-03-25 08:37:51 +08:00
|
|
|
auto bstrUrl = ntcvt::from_chars(sourceUri);
|
|
|
|
|
|
|
|
m_readyToPlay = false;
|
|
|
|
m_state = MEMediaState::Preparing;
|
|
|
|
if (m_mediaEngine)
|
|
|
|
return SUCCEEDED(m_mediaEngine->SetSource(bstrUrl.data()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::Close()
|
|
|
|
{
|
|
|
|
if (m_state == MEMediaState::Closed)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (m_mediaEngine)
|
|
|
|
{
|
|
|
|
m_state = MEMediaState::Closed;
|
|
|
|
return SUCCEEDED(m_mediaEngine->SetSource(NULL));
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::Stop()
|
|
|
|
{
|
|
|
|
if (m_mediaEngine)
|
|
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_state == MEMediaState::Playing)
|
|
|
|
{
|
|
|
|
m_stopping = true;
|
|
|
|
hr = m_mediaEngine->Pause();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (m_state == MEMediaState::Paused)
|
|
|
|
{
|
|
|
|
m_state = MEMediaState::Stopped;
|
|
|
|
FireMediaEvent(MEMediaEventType::Stopped);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return SUCCEEDED(hr);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MfMediaEngine::SetCurrentTime(double fPosInSeconds)
|
|
|
|
{
|
|
|
|
if (m_mediaEngine)
|
|
|
|
return SUCCEEDED(m_mediaEngine->SetCurrentTime(fPosInSeconds));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-04-02 01:51:36 +08:00
|
|
|
bool MfMediaEngine::TransferVideoFrame(std::function<void(const MEVideoFrame&)> callback)
|
2023-03-25 08:37:51 +08:00
|
|
|
{
|
|
|
|
if (m_mediaEngine != nullptr && m_state == MEMediaState::Playing)
|
|
|
|
{
|
2023-04-02 01:51:36 +08:00
|
|
|
do
|
2023-03-25 08:37:51 +08:00
|
|
|
{
|
2023-04-02 01:51:36 +08:00
|
|
|
LONGLONG pts;
|
|
|
|
AX_BREAK_IF(FAILED(m_mediaEngine->OnVideoStreamTick(&pts)));
|
|
|
|
|
2023-03-25 08:37:51 +08:00
|
|
|
const MFVideoNormalizedRect rect{0, 0, 1.0, 1.0};
|
|
|
|
const RECT rcTarget{0, 0, m_videoExtent.x, m_videoExtent.y};
|
|
|
|
HRESULT hr = m_mediaEngine->TransferVideoFrame(m_wicBitmap.Get(), &rect, &rcTarget, &m_bkgColor);
|
2023-04-02 01:51:36 +08:00
|
|
|
AX_BREAK_IF(FAILED(hr));
|
|
|
|
|
|
|
|
ComPtr<IWICBitmapLock> lockedData;
|
|
|
|
DWORD flags = WICBitmapLockRead;
|
|
|
|
WICRect srcRect{0, 0, m_videoExtent.x, m_videoExtent.y};
|
|
|
|
|
|
|
|
AX_BREAK_IF(FAILED(m_wicBitmap->Lock(&srcRect, flags, lockedData.GetAddressOf())));
|
2023-03-25 08:37:51 +08:00
|
|
|
|
2023-04-02 01:51:36 +08:00
|
|
|
UINT stride{0};
|
|
|
|
AX_BREAK_IF(FAILED(lockedData->GetStride(&stride)));
|
|
|
|
|
|
|
|
UINT bufferSize{0};
|
|
|
|
BYTE* data{nullptr};
|
|
|
|
AX_BREAK_IF(FAILED(lockedData->GetDataPointer(&bufferSize, &data)));
|
|
|
|
|
|
|
|
callback(MEVideoFrame{data, nullptr, bufferSize, MEVideoPixelDesc{MEVideoPixelFormat::BGR32, m_videoExtent},
|
|
|
|
m_videoExtent});
|
|
|
|
return true;
|
|
|
|
} while (false);
|
|
|
|
}
|
2023-03-25 08:37:51 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MfMediaEngine::OnMediaEngineEvent(uint32_t meEvent)
|
|
|
|
{
|
|
|
|
switch (meEvent)
|
|
|
|
{
|
|
|
|
case MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA:
|
|
|
|
m_readyToPlay = true;
|
|
|
|
UpdateVideoExtent();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MF_MEDIA_ENGINE_EVENT_CANPLAY:
|
|
|
|
|
|
|
|
// Here we auto-play when ready...
|
|
|
|
if (m_readyToPlay)
|
|
|
|
{
|
|
|
|
FireMediaEvent(MEMediaEventType::Playing);
|
|
|
|
Play();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MF_MEDIA_ENGINE_EVENT_PLAY:
|
|
|
|
m_state = MEMediaState::Playing;
|
|
|
|
FireMediaEvent(MEMediaEventType::Playing);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MF_MEDIA_ENGINE_EVENT_PAUSE:
|
|
|
|
if (!m_stopping)
|
|
|
|
{
|
|
|
|
m_state = MEMediaState::Paused;
|
|
|
|
FireMediaEvent(MEMediaEventType::Paused);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_state = MEMediaState::Stopped;
|
|
|
|
FireMediaEvent(MEMediaEventType::Stopped);
|
|
|
|
|
|
|
|
m_stopping = false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MF_MEDIA_ENGINE_EVENT_ENDED:
|
|
|
|
m_state = MEMediaState::Completed;
|
|
|
|
FireMediaEvent(MEMediaEventType::Completed);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MF_MEDIA_ENGINE_EVENT_ERROR:
|
|
|
|
m_state = MEMediaState::Error;
|
|
|
|
FireMediaEvent(MEMediaEventType::Error);
|
|
|
|
# ifdef _DEBUG
|
|
|
|
if (m_mediaEngine)
|
|
|
|
{
|
|
|
|
ComPtr<IMFMediaError> error;
|
|
|
|
if (SUCCEEDED(m_mediaEngine->GetError(&error)))
|
|
|
|
{
|
|
|
|
USHORT errorCode = error->GetErrorCode();
|
|
|
|
HRESULT hr = error->GetExtendedErrorCode();
|
|
|
|
char buff[128] = {};
|
|
|
|
sprintf_s(buff, "ERROR: Media Foundation Event Error %u (%08X)\n", errorCode,
|
|
|
|
static_cast<unsigned int>(hr));
|
|
|
|
OutputDebugStringA(buff);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
OutputDebugStringA("ERROR: Media Foundation Event Error *FAILED GetError*\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# endif
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MfMediaEngine::UpdateVideoExtent()
|
|
|
|
{
|
|
|
|
if (m_mediaEngine && m_readyToPlay)
|
|
|
|
{
|
2023-04-02 01:51:36 +08:00
|
|
|
DWORD x = 0, y = 0;
|
2023-03-25 08:37:51 +08:00
|
|
|
DX::ThrowIfFailed(m_mediaEngine->GetNativeVideoSize(&x, &y));
|
|
|
|
|
|
|
|
int mods = 0;
|
|
|
|
if (m_videoExtent.x != x)
|
|
|
|
{
|
2023-04-02 01:51:36 +08:00
|
|
|
m_videoExtent.x = x;
|
2023-03-25 08:37:51 +08:00
|
|
|
++mods;
|
|
|
|
}
|
|
|
|
if (m_videoExtent.x != y)
|
|
|
|
{
|
|
|
|
m_videoExtent.y = y;
|
|
|
|
++mods;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mods)
|
|
|
|
DX::ThrowIfFailed(m_wicFactory->CreateBitmap(x, y, GUID_WICPixelFormat32bppBGRA, WICBitmapCacheOnDemand,
|
|
|
|
m_wicBitmap.ReleaseAndGetAddressOf()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_AX_END
|
|
|
|
|
|
|
|
#endif
|