Browse Source

[WIP] Native AVI Player: WinRT version for UWP

Lou Yihua 7 years ago
parent
commit
5dadbb00ac

+ 1 - 1
aviplay.c

@@ -663,7 +663,7 @@ PAL_PlayAVI(
 		//
 		// Try play AVI through OS-native interface
 		//
-		return PAL_PlayAVI_Native(lpszPath);
+		return PAL_PlayAVI_Native(va("%s%s", gConfig.pszGamePath, lpszPath));
 	}
 
     PAL_ClearKeyState();

+ 5 - 0
winrt/SDLPal.Common/WinRTUtil.cpp

@@ -6,6 +6,7 @@
 #include <list>
 #include <DXGI.h>
 #include <ppltasks.h>
+#include <mfapi.h>
 #include "../SDLPal.Common/AsyncHelper.h"
 #include "../SDLPal.Common/StringHelper.h"
 #include "../../main.h"
@@ -186,11 +187,15 @@ INT UTIL_Platform_Init(int argc, char* argv[])
 	}
 	for (auto i = invalid_tokens.begin(); i != invalid_tokens.end(); fal->Remove(*i++));
 
+	MFStartup(MF_VERSION, MFSTARTUP_LITE);
+
 	return 0;
 }
 
 extern "C"
 VOID UTIL_Platform_Quit(VOID)
 {
+	MFShutdown();
+
 	DeleteRunningFile();
 }

+ 609 - 0
winrt/SDLPal.Common/native_avi.cpp

@@ -0,0 +1,609 @@
+/* -*- 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:
+	bstr_t(const wchar_t* s) : m_bstr(SysAllocString(s)) {}
+	~bstr_t() { SysFreeString(m_bstr); }
+
+	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)
+	{
+		InitializeCriticalSection(&m_cs);
+	}
+
+	~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 (WaitForSingleObject(m_event, INFINITE) == 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 (WaitForSingleObject(m_event, 0) == 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;
+}

+ 30 - 0
winrt/SDLPal.UWP.sln

@@ -27,6 +27,9 @@ Global
 		Release|ARM = Release|ARM
 		Release|x64 = Release|x64
 		Release|x86 = Release|x86
+		Test|ARM = Test|ARM
+		Test|x64 = Test|x64
+		Test|x86 = Test|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Debug|ARM.ActiveCfg = Debug|ARM
@@ -41,6 +44,12 @@ Global
 		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Release|x64.Build.0 = Release|x64
 		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Release|x86.ActiveCfg = Release|Win32
 		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Release|x86.Build.0 = Release|Win32
+		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Test|ARM.ActiveCfg = Release|ARM
+		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Test|ARM.Build.0 = Release|ARM
+		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Test|x64.ActiveCfg = Release|x64
+		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Test|x64.Build.0 = Release|x64
+		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Test|x86.ActiveCfg = Release|Win32
+		{96D34C5D-CE4D-43F7-822C-C34775AAFEA8}.Test|x86.Build.0 = Release|Win32
 		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Debug|ARM.ActiveCfg = Debug|ARM
 		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Debug|ARM.Build.0 = Debug|ARM
 		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Debug|x64.ActiveCfg = Debug|x64
@@ -53,6 +62,12 @@ Global
 		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Release|x64.Build.0 = Release|x64
 		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Release|x86.ActiveCfg = Release|Win32
 		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Release|x86.Build.0 = Release|Win32
+		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Test|ARM.ActiveCfg = Release|ARM
+		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Test|ARM.Build.0 = Release|ARM
+		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Test|x64.ActiveCfg = Release|x64
+		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Test|x64.Build.0 = Release|x64
+		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Test|x86.ActiveCfg = Release|Win32
+		{0425A2D9-BB80-4C35-8C69-1DA6E3494FA6}.Test|x86.Build.0 = Release|Win32
 		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Debug|ARM.ActiveCfg = Debug|ARM
 		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Debug|ARM.Build.0 = Debug|ARM
 		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Debug|ARM.Deploy.0 = Debug|ARM
@@ -71,6 +86,15 @@ Global
 		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Release|x86.ActiveCfg = Release|Win32
 		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Release|x86.Build.0 = Release|Win32
 		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Release|x86.Deploy.0 = Release|Win32
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|ARM.ActiveCfg = Release|ARM
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|ARM.Build.0 = Release|ARM
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|ARM.Deploy.0 = Release|ARM
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|x64.ActiveCfg = Release|x64
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|x64.Build.0 = Release|x64
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|x64.Deploy.0 = Release|x64
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|x86.ActiveCfg = Release|Win32
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|x86.Build.0 = Release|Win32
+		{B1809B68-0D9A-4E0E-ADDB-272CA1C9FA38}.Test|x86.Deploy.0 = Release|Win32
 		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Debug|ARM.ActiveCfg = Debug|ARM
 		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Debug|ARM.Build.0 = Debug|ARM
 		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Debug|x64.ActiveCfg = Debug|x64
@@ -83,6 +107,12 @@ Global
 		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Release|x64.Build.0 = Release|x64
 		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Release|x86.ActiveCfg = Release|Win32
 		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Release|x86.Build.0 = Release|Win32
+		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Test|ARM.ActiveCfg = Release|ARM
+		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Test|ARM.Build.0 = Release|ARM
+		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Test|x64.ActiveCfg = Release|x64
+		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Test|x64.Build.0 = Release|x64
+		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Test|x86.ActiveCfg = Release|Win32
+		{89E9B32E-A86A-47C3-A948-D2B1622925CE}.Test|x86.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 7 - 6
winrt/SDLPal.UWP/SDLPal.UWP.vcxproj

@@ -142,7 +142,7 @@
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>$(OutDir)../SDLPal.Common;$(OutDir)../SDLPal.Core</AdditionalLibraryDirectories>
-      <AdditionalDependencies>dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlibd.lib;msvcrtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>Mf.lib;Mfuuid.lib;dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlibd.lib;msvcrtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreSpecificDefaultLibraries>vccorlibd.lib;msvcrtd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
     </Link>
   </ItemDefinitionGroup>
@@ -155,7 +155,7 @@
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>$(OutDir)../SDLPal.Common;$(OutDir)../SDLPal.Core</AdditionalLibraryDirectories>
-      <AdditionalDependencies>dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlib.lib;msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>Mf.lib;Mfuuid.lib;dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlib.lib;msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreSpecificDefaultLibraries>vccorlib.lib;msvcrt.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
     </Link>
   </ItemDefinitionGroup>
@@ -168,7 +168,7 @@
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>$(OutDir)../SDLPal.Common;$(OutDir)../SDLPal.Core</AdditionalLibraryDirectories>
-      <AdditionalDependencies>dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlibd.lib;msvcrtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>Mf.lib;Mfuuid.lib;dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlibd.lib;msvcrtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreSpecificDefaultLibraries>vccorlibd.lib;msvcrtd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
     </Link>
   </ItemDefinitionGroup>
@@ -181,7 +181,7 @@
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>$(OutDir)../SDLPal.Common;$(OutDir)../SDLPal.Core</AdditionalLibraryDirectories>
-      <AdditionalDependencies>dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlib.lib;msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>Mf.lib;Mfuuid.lib;dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlib.lib;msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreSpecificDefaultLibraries>vccorlib.lib;msvcrt.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
     </Link>
   </ItemDefinitionGroup>
@@ -194,7 +194,7 @@
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>$(OutDir)../SDLPal.Common;$(OutDir)../SDLPal.Core</AdditionalLibraryDirectories>
-      <AdditionalDependencies>dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlibd.lib;msvcrtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>Mf.lib;Mfuuid.lib;dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlibd.lib;msvcrtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreSpecificDefaultLibraries>vccorlibd.lib;msvcrtd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
     </Link>
   </ItemDefinitionGroup>
@@ -207,7 +207,7 @@
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>$(OutDir)../SDLPal.Common;$(OutDir)../SDLPal.Core</AdditionalLibraryDirectories>
-      <AdditionalDependencies>dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlib.lib;msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>Mf.lib;Mfuuid.lib;dxgi.lib;dxguid.lib;sdlpal.common.lib;sdlpal.core.lib;vccorlib.lib;msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreSpecificDefaultLibraries>vccorlib.lib;msvcrt.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
     </Link>
   </ItemDefinitionGroup>
@@ -238,6 +238,7 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\native_midi.cpp" />
+    <ClCompile Include="..\SDLPal.Common\native_avi.cpp" />
     <ClCompile Include="..\SDLPal.Common\SDLPal.cpp" />
     <ClCompile Include="..\SDLPal.Common\WinRTUtil.cpp" />
     <ClCompile Include="App.xaml.cpp">

+ 1 - 0
winrt/SDLPal.UWP/SDLPal.UWP.vcxproj.filters

@@ -35,6 +35,7 @@
       <Filter>Common</Filter>
     </ClCompile>
     <ClCompile Include="..\native_midi.cpp" />
+    <ClCompile Include="..\SDLPal.Common\native_avi.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="pch.h" />

+ 1 - 1
winrt/pal_config.h

@@ -46,10 +46,10 @@
 #define PAL_PORTYEAR         "2015"
 
 #define PAL_HAS_CONFIG_PAGE  1
-
 #if NTDDI_VERSION >= NTDDI_WIN10
 # define PAL_HAS_NATIVEMIDI  1
 #endif
+#define PAL_HAS_NATIVEAVI    1
 
 #define PAL_FILESYSTEM_IGNORE_CASE 1