123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- /* -*- mode: c; tab-width: 4; c-basic-offset: 4; c-file-style: "linux" -*- */
- //
- // Copyright (c) 2017, SDLPAL development team.
- // All rights reserved.
- //
- // This file is part of SDLPAL.
- //
- // SDLPAL is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program. If not, see <http://www.gnu.org/licenses/>.
- //
- // native_avi.cpp: Native Windows Runtime AVI player for SDLPal.
- // @Author: Lou Yihua, 2017
- //
- #include "pch.h"
- #include "AsyncHelper.h"
- #include "StringHelper.h"
- #include <mfapi.h>
- #include <mfidl.h>
- #include <mfmediaengine.h>
- #include <wrl.h>
- #include <d3d11.h>
- #include <list>
- #include <map>
- #include "../../3rd/SDL/include/SDL_syswm.h"
- #include "../../video.h"
- #include "../../util.h"
- #include "../../input.h"
- using namespace Microsoft::WRL;
- class bstr_t
- {
- public:
- #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
- struct _bstr
- {
- DWORD length;
- OLECHAR string[2];
- };
- bstr_t(const wchar_t* s) : m_bstr(nullptr)
- {
- DWORD length = (DWORD)wcslen(s);
- auto p = (_bstr*)CoTaskMemAlloc(length * sizeof(wchar_t) + sizeof(_bstr));
- wcsncpy(m_bstr = p->string, s, length);
- p->string[length] = p->string[length + 1] = L'\0';
- p->length = length * sizeof(wchar_t);
- }
- ~bstr_t() { CoTaskMemFree((LPBYTE)m_bstr - offsetof(_bstr, string)); }
- #else
- bstr_t(const wchar_t* s) : m_bstr(SysAllocString(s)) {}
- ~bstr_t() { SysFreeString(m_bstr); }
- #endif
- operator BSTR() { return m_bstr; }
- private:
- BSTR m_bstr;
- };
- class CUnknown
- : public IUnknown
- {
- public:
- CUnknown() : m_refcount(1) { }
- virtual ~CUnknown() { }
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
- {
- HRESULT hr = S_OK;
- if (riid == IID_IUnknown)
- *ppvObject = this, AddRef();
- else
- hr = E_NOINTERFACE;
- return hr;
- }
- virtual ULONG STDMETHODCALLTYPE AddRef(void)
- {
- return (ULONG)InterlockedIncrement(&m_refcount);
- }
- virtual ULONG STDMETHODCALLTYPE Release(void)
- {
- LONG ref = InterlockedDecrement(&m_refcount);
- if (0 == ref)
- {
- delete this;
- }
- return ref;
- }
- private:
- volatile LONG m_refcount;
- };
- class CAVIPlayer
- : public CUnknown
- , public IMFMediaEngineNotify
- , public IMFMediaEngineExtension
- {
- #define FAIL_RETURN(x) do { HRESULT hr; if (hr = (x)) return hr; } while(0)
- struct Event
- {
- DWORD event;
- DWORD_PTR param1;
- DWORD param2;
- Event(DWORD a, DWORD_PTR b, DWORD c) : event(a), param1(b), param2(c) {}
- };
- class CMFAsyncCallback
- : public CUnknown
- , public IMFAsyncCallback
- {
- public:
- CMFAsyncCallback(IMFByteStream* pByteStream, IMFSourceResolver* resolver, IUnknown* punkState, IMFAsyncCallback* pCallback)
- : m_path(nullptr), m_pByteStream(pByteStream), m_punkState(punkState), m_pCallback(pCallback), m_resolver(resolver)
- {
- }
- CMFAsyncCallback(BSTR bstrURL, IUnknown* punkState, IMFAsyncCallback* pCallback)
- : m_path(bstrURL ? ref new Platform::String(bstrURL) : nullptr)
- , m_pByteStream(nullptr), m_punkState(punkState)
- , m_pCallback(pCallback), m_resolver(nullptr)
- {
- }
- CMFAsyncCallback(BSTR bstrURL, IMFSourceResolver* resolver, IUnknown* punkState, IMFAsyncCallback* pCallback)
- : m_path(bstrURL ? ref new Platform::String(bstrURL) : nullptr)
- , m_pByteStream(nullptr), m_punkState(punkState)
- , m_pCallback(pCallback), m_resolver(resolver)
- {
- }
- virtual ~CMFAsyncCallback()
- {
- if (m_path) delete m_path;
- }
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
- {
- HRESULT hr = S_OK;
- if (riid == IID_IMFAsyncCallback)
- *ppvObject = static_cast<IMFAsyncCallback*>(this), AddRef();
- else
- hr = CUnknown::QueryInterface(riid, ppvObject);
- return hr;
- }
- virtual ULONG STDMETHODCALLTYPE AddRef(void) { return CUnknown::AddRef(); }
- virtual ULONG STDMETHODCALLTYPE Release(void) { return CUnknown::Release(); }
- virtual HRESULT STDMETHODCALLTYPE GetParameters(
- __RPC__out DWORD *pdwFlags,
- __RPC__out DWORD *pdwQueue)
- {
- return E_NOTIMPL;
- }
- virtual HRESULT STDMETHODCALLTYPE Invoke(__RPC__in_opt IMFAsyncResult *pAsyncResult)
- {
- HRESULT hr;
- if (m_path != nullptr)
- {
- hr = CreateMFByteStreamFromURL(m_path, m_pByteStream.ReleaseAndGetAddressOf());
- if (m_resolver.Get() == nullptr)
- {
- return InvokeCallback(m_pByteStream.Get(), hr);
- }
- }
- ComPtr<IUnknown> result;
- MF_OBJECT_TYPE type;
- hr = m_resolver->CreateObjectFromByteStream(m_pByteStream.Get(), nullptr, MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_READ, nullptr, &type, result.GetAddressOf());
- return InvokeCallback(result.Get(), hr);
- }
- protected:
- HRESULT InvokeCallback(IUnknown* pObject, HRESULT hr)
- {
- ComPtr<IMFAsyncResult> pResult;
- HRESULT nhr = MFCreateAsyncResult(pObject, m_pCallback.Get(), m_punkState.Get(), pResult.GetAddressOf());
- if (SUCCEEDED(nhr))
- {
- pResult->SetStatus(hr);
- nhr = MFInvokeCallback(pResult.Get());
- }
- return nhr;
- }
- static HRESULT CreateMFByteStreamFromURL(Platform::String^ path, IMFByteStream** ppByteStream)
- {
- HRESULT hr;
- if (!ppByteStream) return E_POINTER;
- try
- {
- auto file = AWait(Windows::Storage::StorageFile::GetFileFromPathAsync(path));
- auto stream = AWait(file->OpenAsync(Windows::Storage::FileAccessMode::Read));
- hr = MFCreateMFByteStreamOnStreamEx(reinterpret_cast<IUnknown*>(stream), ppByteStream);
- delete stream;
- delete file;
- }
- catch (Exception ^e)
- {
- hr = e->HResult;
- }
- return hr;
- }
- private:
- Platform::String^ m_path;
- ComPtr<IMFByteStream> m_pByteStream;
- ComPtr<IUnknown> m_punkState;
- ComPtr<IMFAsyncCallback> m_pCallback;
- ComPtr<IMFSourceResolver> m_resolver;
- };
- public:
- CAVIPlayer()
- : m_event(CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS))
- , m_playing(false)
- {
- InitializeCriticalSectionEx(&m_cs, 4000, 0);
- }
- ~CAVIPlayer()
- {
- DeleteCriticalSection(&m_cs);
- }
- HRESULT Initialize()
- {
- ComPtr<ID3D10Multithread> mtctx;
- ComPtr<IMFMediaEngineClassFactory> mfmecf;
- ComPtr<IMFAttributes> mfattr;
- static const D3D_FEATURE_LEVEL featureLevels[] =
- {
- D3D_FEATURE_LEVEL_11_1,
- D3D_FEATURE_LEVEL_11_0,
- D3D_FEATURE_LEVEL_10_1,
- D3D_FEATURE_LEVEL_10_0,
- D3D_FEATURE_LEVEL_9_3,
- D3D_FEATURE_LEVEL_9_1
- };
- FAIL_RETURN(D3D11CreateDevice(
- nullptr, // specify nullptr to use the default adapter
- D3D_DRIVER_TYPE_HARDWARE,
- nullptr, // leave as nullptr if hardware is used
- D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
- featureLevels,
- ARRAYSIZE(featureLevels),
- D3D11_SDK_VERSION, // always set this to D3D11_SDK_VERSION
- m_device.ReleaseAndGetAddressOf(),
- nullptr,
- m_context.ReleaseAndGetAddressOf()
- ));
- FAIL_RETURN(m_context.As(&mtctx));
- FAIL_RETURN(mtctx->SetMultithreadProtected(TRUE));
- UINT resetToken;
- FAIL_RETURN(MFCreateDXGIDeviceManager(&resetToken, m_manager.ReleaseAndGetAddressOf()));
- FAIL_RETURN(m_manager->ResetDevice(m_device.Get(), resetToken));
- FAIL_RETURN(CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_ALL, IID_PPV_ARGS(mfmecf.GetAddressOf())));
- FAIL_RETURN(MFCreateAttributes(mfattr.GetAddressOf(), 1));
- FAIL_RETURN(mfattr->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, static_cast<CUnknown*>(this)));
- FAIL_RETURN(mfattr->SetUnknown(MF_MEDIA_ENGINE_EXTENSION, static_cast<CUnknown*>(this)));
- FAIL_RETURN(mfattr->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER, m_manager.Get()));
- FAIL_RETURN(mfattr->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM));
- FAIL_RETURN(mfmecf->CreateInstance(0, mfattr.Get(), m_engine.ReleaseAndGetAddressOf()));
- FAIL_RETURN(MFCreateSourceResolver(m_resolver.GetAddressOf()));
- m_playing = false;
- ResetEvent(m_event);
- m_events.clear();
- return S_OK;
- }
- //HRESULT
- HRESULT Load(Platform::String^ filePath, DWORD* cx, DWORD* cy)
- {
- if (!filePath || !cx || !cy) return E_POINTER;
- if (m_engine)
- {
- ResetEvent(m_event);
- FAIL_RETURN(m_engine->SetSource(bstr_t(filePath->Data())));
- FAIL_RETURN(m_engine->Load());
- bool abort = false, ready = false;
- while (WaitForSingleObjectEx(m_event, INFINITE, FALSE) == WAIT_OBJECT_0 && !abort && !ready)
- {
- EnterCriticalSection(&m_cs);
- for (auto i = m_events.begin(); i != m_events.end(); i = m_events.erase(i))
- {
- switch (i->event)
- {
- case MF_MEDIA_ENGINE_EVENT_ERROR:
- UTIL_LogOutput(LOGLEVEL_ERROR, "Native AVI player encountered error: %d (%08x)!", i->param1, i->param2);
- break;
- case MF_MEDIA_ENGINE_EVENT_ABORT:
- abort = true;
- break;
- case MF_MEDIA_ENGINE_EVENT_CANPLAY:
- ready = true;
- break;
- }
- }
- LeaveCriticalSection(&m_cs);
- }
- if (abort || !ready) return E_FAIL;
- FAIL_RETURN(hr = m_engine->GetNativeVideoSize(cx, cy));
- D3D11_TEXTURE2D_DESC desc = {
- *cx,
- *cy,
- 1,
- 1,
- DXGI_FORMAT_B8G8R8A8_UNORM,
- { 1, 0 },
- D3D11_USAGE_DEFAULT,
- D3D11_BIND_RENDER_TARGET,
- 0,
- 0
- };
- FAIL_RETURN(m_device->CreateTexture2D(&desc, nullptr, m_render.ReleaseAndGetAddressOf()));
- desc.Usage = D3D11_USAGE_STAGING;
- desc.BindFlags = 0;
- desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
- FAIL_RETURN(m_device->CreateTexture2D(&desc, nullptr, m_reader.ReleaseAndGetAddressOf()));
- return S_OK;
- }
- return E_FAIL;
- }
- template<typename _Callback>
- HRESULT Play(_Callback callback)
- {
- HRESULT hr = E_FAIL;
- if (m_engine && SUCCEEDED(hr = m_engine->Play()))
- {
- MFVideoNormalizedRect rect = { 0.0f, 0.0f, 1.0f, 1.0f };
- RECT rcTarget = { 0 };
- MFARGB bkgColor = { 0 };
- bool playing = true, skip = false;
- m_engine->GetNativeVideoSize((DWORD*)&rcTarget.right, (DWORD*)&rcTarget.bottom);
- while (playing && !skip)
- {
- LONGLONG pts;
- if (m_engine->OnVideoStreamTick(&pts) == S_OK &&
- m_engine->TransferVideoFrame(m_render.Get(), &rect, &rcTarget, &bkgColor) == S_OK)
- {
- D3D11_MAPPED_SUBRESOURCE res;
- // new frame available at the media engine so get it
- m_context->CopyResource(m_reader.Get(), m_render.Get());
- m_context->Flush();
- m_context->Map(m_reader.Get(), 0, D3D11_MAP_READ, 0, &res);
- skip = callback(res.pData, res.RowPitch);
- m_context->Unmap(m_reader.Get(), 0);
- }
- if (WaitForSingleObjectEx(m_event, 0, FALSE) == WAIT_OBJECT_0)
- {
- EnterCriticalSection(&m_cs);
- for (auto i = m_events.begin(); i != m_events.end(); i = m_events.erase(i))
- {
- if (i->event == MF_MEDIA_ENGINE_EVENT_ENDED)
- {
- playing = false;
- break;
- }
- }
- LeaveCriticalSection(&m_cs);
- }
- }
- hr = skip ? S_FALSE : S_OK;
- }
- return hr;
- }
- HRESULT Pause()
- {
- if (m_engine)
- {
- HRESULT hr = m_engine->Pause();
- if (m_playing && SUCCEEDED(hr)) m_playing = false;
- return hr;
- }
- else
- {
- m_playing = false;
- return E_NOT_VALID_STATE;
- }
- }
- HRESULT Shutdown()
- {
- if (m_engine)
- {
- HRESULT hr = m_engine->Shutdown();
- if (m_playing && SUCCEEDED(hr)) m_playing = false;
- return hr;
- }
- else
- {
- m_playing = false;
- return E_NOT_VALID_STATE;
- }
- }
- bool IsPlaying() const
- {
- return m_playing;
- }
- // ***************************************************************************************
- // IUnknown
- // ***************************************************************************************
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
- {
- HRESULT hr = S_OK;
- if (!ppvObject) return E_POINTER;
- if (riid == IID_IMFMediaEngineNotify)
- *ppvObject = static_cast<IMFMediaEngineNotify*>(this);
- else if (riid == IID_IMFMediaEngineExtension)
- *ppvObject = static_cast<IMFMediaEngineExtension*>(this);
- else
- hr = CUnknown::QueryInterface(riid, ppvObject);
- return hr;
- }
- virtual ULONG STDMETHODCALLTYPE AddRef(void)
- {
- return 1;
- }
- virtual ULONG STDMETHODCALLTYPE Release(void)
- {
- return 1;
- }
- // ***************************************************************************************
- // IMFMediaEngineNotify
- // ***************************************************************************************
- virtual HRESULT STDMETHODCALLTYPE EventNotify(DWORD event, DWORD_PTR param1, DWORD param2)
- {
- EnterCriticalSection(&m_cs);
- if (event == MF_MEDIA_ENGINE_EVENT_ERROR)
- {
- ComPtr<IMFMediaError> error;
- m_engine->GetError(&error);
- m_events.push_back(Event(event, error->GetErrorCode(), error->GetExtendedErrorCode()));
- }
- else
- {
- m_events.push_back(Event(event, param1, param2));
- }
- LeaveCriticalSection(&m_cs);
- SetEvent(m_event);
- return S_OK;
- }
- // ***************************************************************************************
- // IMFMediaEngineExtension
- // ***************************************************************************************
- virtual HRESULT STDMETHODCALLTYPE CanPlayType(
- _In_ BOOL AudioOnly,
- _In_ BSTR MimeType,
- _Out_ MF_MEDIA_ENGINE_CANPLAY *pAnswer)
- {
- if (!pAnswer) return E_POINTER;
- if (_wcsicmp(MimeType, L"video/avi") == 0)
- *pAnswer = MF_MEDIA_ENGINE_CANPLAY_PROBABLY;
- else
- *pAnswer = MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED;
- return S_OK;
- }
- virtual HRESULT STDMETHODCALLTYPE BeginCreateObject(
- _In_ BSTR bstrURL,
- _In_opt_ IMFByteStream *pByteStream,
- _In_ MF_OBJECT_TYPE type,
- _Outptr_ IUnknown **ppIUnknownCancelCookie,
- _In_ IMFAsyncCallback *pCallback,
- _In_opt_ IUnknown *punkState)
- {
- if (!pCallback) return E_POINTER;
- switch (type)
- {
- case MF_OBJECT_BYTESTREAM:
- if (!bstrURL) return E_POINTER;
- break;
- case MF_OBJECT_MEDIASOURCE:
- if (!bstrURL && !pByteStream) return E_POINTER;
- break;
- default:
- return E_INVALIDARG;
- }
- ComPtr<IMFAsyncCallback> pcb;
- if (type == MF_OBJECT_BYTESTREAM)
- {
- if (!pByteStream)
- {
- pcb.Attach(new CMFAsyncCallback(bstrURL, punkState, pCallback));
- }
- else
- {
- return E_INVALIDARG;
- }
- }
- if (type == MF_OBJECT_MEDIASOURCE)
- {
- if (!pByteStream)
- {
- pcb.Attach(new CMFAsyncCallback(bstrURL, m_resolver.Get(), punkState, pCallback));
- }
- else
- {
- pcb.Attach(new CMFAsyncCallback(pByteStream, m_resolver.Get(), punkState, pCallback));
- }
- }
- return MFPutWorkItem2(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, pcb.Get(), nullptr);
- }
- virtual HRESULT STDMETHODCALLTYPE CancelObjectCreation(
- _In_ IUnknown *pIUnknownCancelCookie)
- {
- return S_OK;
- }
- virtual HRESULT STDMETHODCALLTYPE EndCreateObject(
- _In_ IMFAsyncResult *pResult,
- _Outptr_ IUnknown **ppObject)
- {
- return pResult->GetObject(ppObject);
- }
- private:
- HANDLE m_event;
- CRITICAL_SECTION m_cs;
- ComPtr<ID3D11Device> m_device;
- ComPtr<ID3D11DeviceContext> m_context;
- ComPtr<IMFDXGIDeviceManager> m_manager;
- ComPtr<IMFMediaEngine> m_engine;
- ComPtr<IMFSourceResolver> m_resolver;
- ComPtr<ID3D11Texture2D> m_render, m_reader;
- std::list<Event> m_events;
- bool m_playing;
- #undef FAIL_RETURN
- };
- extern "C" BOOL PAL_PlayAVI_Native(const char *lpszPath)
- {
- CAVIPlayer player;
- HRESULT hr;
- DWORD cx, cy;
- if (FAILED(hr = player.Initialize())) return FALSE;
- if (FAILED(hr = player.Load(ConvertString(lpszPath), &cx, &cy))) return FALSE;
- PAL_ClearKeyState();
- SDL_Surface* surface = SDL_CreateRGBSurface(SDL_SWSURFACE, cx, cy, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
- hr = player.Play([surface](const void* data, UINT pitch)->bool {
- if (pitch == surface->pitch)
- {
- memcpy(surface->pixels, data, surface->pitch * surface->h);
- }
- else
- {
- auto dst = (uint8_t*)surface->pixels - surface->pitch, src = (uint8_t*)data - pitch;
- auto length = surface->w << 2;
- for (int i = 0; i < surface->h; i++, memcpy(dst += surface->pitch, src += pitch, length));
- }
- VIDEO_DrawSurfaceToScreen(surface);
- UTIL_Delay(1);
- return (g_InputState.dwKeyPress & (kKeyMenu | kKeySearch)) != 0;
- });
- SDL_FreeSurface(surface);
- if (FAILED(hr)) return FALSE;
- if (hr == S_FALSE)
- {
- //
- // Simulate a short delay (like the original game)
- //
- UTIL_Delay(500);
- }
- hr = player.Pause();
- hr = player.Shutdown();
- return TRUE;
- }
|