native_avi.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. /* -*- mode: c; tab-width: 4; c-basic-offset: 4; c-file-style: "linux" -*- */
  2. //
  3. // Copyright (c) 2017, SDLPAL development team.
  4. // All rights reserved.
  5. //
  6. // This file is part of SDLPAL.
  7. //
  8. // SDLPAL is free software: you can redistribute it and/or modify
  9. // it under the terms of the GNU General Public License as published by
  10. // the Free Software Foundation, either version 3 of the License, or
  11. // (at your option) any later version.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU General Public License
  19. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. //
  21. // native_avi.cpp: Native Windows Runtime AVI player for SDLPal.
  22. // @Author: Lou Yihua, 2017
  23. //
  24. #include "pch.h"
  25. #include "AsyncHelper.h"
  26. #include "StringHelper.h"
  27. #include <mfapi.h>
  28. #include <mfidl.h>
  29. #include <mfmediaengine.h>
  30. #include <wrl.h>
  31. #include <d3d11.h>
  32. #include <list>
  33. #include <map>
  34. #include "../../3rd/SDL/include/SDL_syswm.h"
  35. #include "../../video.h"
  36. #include "../../util.h"
  37. #include "../../input.h"
  38. using namespace Microsoft::WRL;
  39. class bstr_t
  40. {
  41. public:
  42. #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
  43. struct _bstr
  44. {
  45. DWORD length;
  46. OLECHAR string[2];
  47. };
  48. bstr_t(const wchar_t* s) : m_bstr(nullptr)
  49. {
  50. DWORD length = (DWORD)wcslen(s);
  51. auto p = (_bstr*)CoTaskMemAlloc(length * sizeof(wchar_t) + sizeof(_bstr));
  52. wcsncpy(m_bstr = p->string, s, length);
  53. p->string[length] = p->string[length + 1] = L'\0';
  54. p->length = length * sizeof(wchar_t);
  55. }
  56. ~bstr_t() { CoTaskMemFree((LPBYTE)m_bstr - offsetof(_bstr, string)); }
  57. #else
  58. bstr_t(const wchar_t* s) : m_bstr(SysAllocString(s)) {}
  59. ~bstr_t() { SysFreeString(m_bstr); }
  60. #endif
  61. operator BSTR() { return m_bstr; }
  62. private:
  63. BSTR m_bstr;
  64. };
  65. class CUnknown
  66. : public IUnknown
  67. {
  68. public:
  69. CUnknown() : m_refcount(1) { }
  70. virtual ~CUnknown() { }
  71. virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
  72. {
  73. HRESULT hr = S_OK;
  74. if (riid == IID_IUnknown)
  75. *ppvObject = this, AddRef();
  76. else
  77. hr = E_NOINTERFACE;
  78. return hr;
  79. }
  80. virtual ULONG STDMETHODCALLTYPE AddRef(void)
  81. {
  82. return (ULONG)InterlockedIncrement(&m_refcount);
  83. }
  84. virtual ULONG STDMETHODCALLTYPE Release(void)
  85. {
  86. LONG ref = InterlockedDecrement(&m_refcount);
  87. if (0 == ref)
  88. {
  89. delete this;
  90. }
  91. return ref;
  92. }
  93. private:
  94. volatile LONG m_refcount;
  95. };
  96. class CAVIPlayer
  97. : public CUnknown
  98. , public IMFMediaEngineNotify
  99. , public IMFMediaEngineExtension
  100. {
  101. #define FAIL_RETURN(x) do { HRESULT hr; if (hr = (x)) return hr; } while(0)
  102. struct Event
  103. {
  104. DWORD event;
  105. DWORD_PTR param1;
  106. DWORD param2;
  107. Event(DWORD a, DWORD_PTR b, DWORD c) : event(a), param1(b), param2(c) {}
  108. };
  109. class CMFAsyncCallback
  110. : public CUnknown
  111. , public IMFAsyncCallback
  112. {
  113. public:
  114. CMFAsyncCallback(IMFByteStream* pByteStream, IMFSourceResolver* resolver, IUnknown* punkState, IMFAsyncCallback* pCallback)
  115. : m_path(nullptr), m_pByteStream(pByteStream), m_punkState(punkState), m_pCallback(pCallback), m_resolver(resolver)
  116. {
  117. }
  118. CMFAsyncCallback(BSTR bstrURL, IUnknown* punkState, IMFAsyncCallback* pCallback)
  119. : m_path(bstrURL ? ref new Platform::String(bstrURL) : nullptr)
  120. , m_pByteStream(nullptr), m_punkState(punkState)
  121. , m_pCallback(pCallback), m_resolver(nullptr)
  122. {
  123. }
  124. CMFAsyncCallback(BSTR bstrURL, IMFSourceResolver* resolver, IUnknown* punkState, IMFAsyncCallback* pCallback)
  125. : m_path(bstrURL ? ref new Platform::String(bstrURL) : nullptr)
  126. , m_pByteStream(nullptr), m_punkState(punkState)
  127. , m_pCallback(pCallback), m_resolver(resolver)
  128. {
  129. }
  130. virtual ~CMFAsyncCallback()
  131. {
  132. if (m_path) delete m_path;
  133. }
  134. virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
  135. {
  136. HRESULT hr = S_OK;
  137. if (riid == IID_IMFAsyncCallback)
  138. *ppvObject = static_cast<IMFAsyncCallback*>(this), AddRef();
  139. else
  140. hr = CUnknown::QueryInterface(riid, ppvObject);
  141. return hr;
  142. }
  143. virtual ULONG STDMETHODCALLTYPE AddRef(void) { return CUnknown::AddRef(); }
  144. virtual ULONG STDMETHODCALLTYPE Release(void) { return CUnknown::Release(); }
  145. virtual HRESULT STDMETHODCALLTYPE GetParameters(
  146. __RPC__out DWORD *pdwFlags,
  147. __RPC__out DWORD *pdwQueue)
  148. {
  149. return E_NOTIMPL;
  150. }
  151. virtual HRESULT STDMETHODCALLTYPE Invoke(__RPC__in_opt IMFAsyncResult *pAsyncResult)
  152. {
  153. HRESULT hr;
  154. if (m_path != nullptr)
  155. {
  156. hr = CreateMFByteStreamFromURL(m_path, m_pByteStream.ReleaseAndGetAddressOf());
  157. if (m_resolver.Get() == nullptr)
  158. {
  159. return InvokeCallback(m_pByteStream.Get(), hr);
  160. }
  161. }
  162. ComPtr<IUnknown> result;
  163. MF_OBJECT_TYPE type;
  164. hr = m_resolver->CreateObjectFromByteStream(m_pByteStream.Get(), nullptr, MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_READ, nullptr, &type, result.GetAddressOf());
  165. return InvokeCallback(result.Get(), hr);
  166. }
  167. protected:
  168. HRESULT InvokeCallback(IUnknown* pObject, HRESULT hr)
  169. {
  170. ComPtr<IMFAsyncResult> pResult;
  171. HRESULT nhr = MFCreateAsyncResult(pObject, m_pCallback.Get(), m_punkState.Get(), pResult.GetAddressOf());
  172. if (SUCCEEDED(nhr))
  173. {
  174. pResult->SetStatus(hr);
  175. nhr = MFInvokeCallback(pResult.Get());
  176. }
  177. return nhr;
  178. }
  179. static HRESULT CreateMFByteStreamFromURL(Platform::String^ path, IMFByteStream** ppByteStream)
  180. {
  181. HRESULT hr;
  182. if (!ppByteStream) return E_POINTER;
  183. try
  184. {
  185. auto file = AWait(Windows::Storage::StorageFile::GetFileFromPathAsync(path));
  186. auto stream = AWait(file->OpenAsync(Windows::Storage::FileAccessMode::Read));
  187. hr = MFCreateMFByteStreamOnStreamEx(reinterpret_cast<IUnknown*>(stream), ppByteStream);
  188. delete stream;
  189. delete file;
  190. }
  191. catch (Exception ^e)
  192. {
  193. hr = e->HResult;
  194. }
  195. return hr;
  196. }
  197. private:
  198. Platform::String^ m_path;
  199. ComPtr<IMFByteStream> m_pByteStream;
  200. ComPtr<IUnknown> m_punkState;
  201. ComPtr<IMFAsyncCallback> m_pCallback;
  202. ComPtr<IMFSourceResolver> m_resolver;
  203. };
  204. public:
  205. CAVIPlayer()
  206. : m_event(CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS))
  207. , m_playing(false)
  208. {
  209. InitializeCriticalSectionEx(&m_cs, 4000, 0);
  210. }
  211. ~CAVIPlayer()
  212. {
  213. DeleteCriticalSection(&m_cs);
  214. }
  215. HRESULT Initialize()
  216. {
  217. ComPtr<ID3D10Multithread> mtctx;
  218. ComPtr<IMFMediaEngineClassFactory> mfmecf;
  219. ComPtr<IMFAttributes> mfattr;
  220. static const D3D_FEATURE_LEVEL featureLevels[] =
  221. {
  222. D3D_FEATURE_LEVEL_11_1,
  223. D3D_FEATURE_LEVEL_11_0,
  224. D3D_FEATURE_LEVEL_10_1,
  225. D3D_FEATURE_LEVEL_10_0,
  226. D3D_FEATURE_LEVEL_9_3,
  227. D3D_FEATURE_LEVEL_9_1
  228. };
  229. FAIL_RETURN(D3D11CreateDevice(
  230. nullptr, // specify nullptr to use the default adapter
  231. D3D_DRIVER_TYPE_HARDWARE,
  232. nullptr, // leave as nullptr if hardware is used
  233. D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
  234. featureLevels,
  235. ARRAYSIZE(featureLevels),
  236. D3D11_SDK_VERSION, // always set this to D3D11_SDK_VERSION
  237. m_device.ReleaseAndGetAddressOf(),
  238. nullptr,
  239. m_context.ReleaseAndGetAddressOf()
  240. ));
  241. FAIL_RETURN(m_context.As(&mtctx));
  242. FAIL_RETURN(mtctx->SetMultithreadProtected(TRUE));
  243. UINT resetToken;
  244. FAIL_RETURN(MFCreateDXGIDeviceManager(&resetToken, m_manager.ReleaseAndGetAddressOf()));
  245. FAIL_RETURN(m_manager->ResetDevice(m_device.Get(), resetToken));
  246. FAIL_RETURN(CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_ALL, IID_PPV_ARGS(mfmecf.GetAddressOf())));
  247. FAIL_RETURN(MFCreateAttributes(mfattr.GetAddressOf(), 1));
  248. FAIL_RETURN(mfattr->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, static_cast<CUnknown*>(this)));
  249. FAIL_RETURN(mfattr->SetUnknown(MF_MEDIA_ENGINE_EXTENSION, static_cast<CUnknown*>(this)));
  250. FAIL_RETURN(mfattr->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER, m_manager.Get()));
  251. FAIL_RETURN(mfattr->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM));
  252. FAIL_RETURN(mfmecf->CreateInstance(0, mfattr.Get(), m_engine.ReleaseAndGetAddressOf()));
  253. FAIL_RETURN(MFCreateSourceResolver(m_resolver.GetAddressOf()));
  254. m_playing = false;
  255. ResetEvent(m_event);
  256. m_events.clear();
  257. return S_OK;
  258. }
  259. //HRESULT
  260. HRESULT Load(Platform::String^ filePath, DWORD* cx, DWORD* cy)
  261. {
  262. if (!filePath || !cx || !cy) return E_POINTER;
  263. if (m_engine)
  264. {
  265. ResetEvent(m_event);
  266. FAIL_RETURN(m_engine->SetSource(bstr_t(filePath->Data())));
  267. FAIL_RETURN(m_engine->Load());
  268. bool abort = false, ready = false;
  269. while (WaitForSingleObjectEx(m_event, INFINITE, FALSE) == WAIT_OBJECT_0 && !abort && !ready)
  270. {
  271. EnterCriticalSection(&m_cs);
  272. for (auto i = m_events.begin(); i != m_events.end(); i = m_events.erase(i))
  273. {
  274. switch (i->event)
  275. {
  276. case MF_MEDIA_ENGINE_EVENT_ERROR:
  277. UTIL_LogOutput(LOGLEVEL_ERROR, "Native AVI player encountered error: %d (%08x)!", i->param1, i->param2);
  278. break;
  279. case MF_MEDIA_ENGINE_EVENT_ABORT:
  280. abort = true;
  281. break;
  282. case MF_MEDIA_ENGINE_EVENT_CANPLAY:
  283. ready = true;
  284. break;
  285. }
  286. }
  287. LeaveCriticalSection(&m_cs);
  288. }
  289. if (abort || !ready) return E_FAIL;
  290. FAIL_RETURN(hr = m_engine->GetNativeVideoSize(cx, cy));
  291. D3D11_TEXTURE2D_DESC desc = {
  292. *cx,
  293. *cy,
  294. 1,
  295. 1,
  296. DXGI_FORMAT_B8G8R8A8_UNORM,
  297. { 1, 0 },
  298. D3D11_USAGE_DEFAULT,
  299. D3D11_BIND_RENDER_TARGET,
  300. 0,
  301. 0
  302. };
  303. FAIL_RETURN(m_device->CreateTexture2D(&desc, nullptr, m_render.ReleaseAndGetAddressOf()));
  304. desc.Usage = D3D11_USAGE_STAGING;
  305. desc.BindFlags = 0;
  306. desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
  307. FAIL_RETURN(m_device->CreateTexture2D(&desc, nullptr, m_reader.ReleaseAndGetAddressOf()));
  308. return S_OK;
  309. }
  310. return E_FAIL;
  311. }
  312. template<typename _Callback>
  313. HRESULT Play(_Callback callback)
  314. {
  315. HRESULT hr = E_FAIL;
  316. if (m_engine && SUCCEEDED(hr = m_engine->Play()))
  317. {
  318. MFVideoNormalizedRect rect = { 0.0f, 0.0f, 1.0f, 1.0f };
  319. RECT rcTarget = { 0 };
  320. MFARGB bkgColor = { 0 };
  321. bool playing = true, skip = false;
  322. m_engine->GetNativeVideoSize((DWORD*)&rcTarget.right, (DWORD*)&rcTarget.bottom);
  323. while (playing && !skip)
  324. {
  325. LONGLONG pts;
  326. if (m_engine->OnVideoStreamTick(&pts) == S_OK &&
  327. m_engine->TransferVideoFrame(m_render.Get(), &rect, &rcTarget, &bkgColor) == S_OK)
  328. {
  329. D3D11_MAPPED_SUBRESOURCE res;
  330. // new frame available at the media engine so get it
  331. m_context->CopyResource(m_reader.Get(), m_render.Get());
  332. m_context->Flush();
  333. m_context->Map(m_reader.Get(), 0, D3D11_MAP_READ, 0, &res);
  334. skip = callback(res.pData, res.RowPitch);
  335. m_context->Unmap(m_reader.Get(), 0);
  336. }
  337. if (WaitForSingleObjectEx(m_event, 0, FALSE) == WAIT_OBJECT_0)
  338. {
  339. EnterCriticalSection(&m_cs);
  340. for (auto i = m_events.begin(); i != m_events.end(); i = m_events.erase(i))
  341. {
  342. if (i->event == MF_MEDIA_ENGINE_EVENT_ENDED)
  343. {
  344. playing = false;
  345. break;
  346. }
  347. }
  348. LeaveCriticalSection(&m_cs);
  349. }
  350. }
  351. hr = skip ? S_FALSE : S_OK;
  352. }
  353. return hr;
  354. }
  355. HRESULT Pause()
  356. {
  357. if (m_engine)
  358. {
  359. HRESULT hr = m_engine->Pause();
  360. if (m_playing && SUCCEEDED(hr)) m_playing = false;
  361. return hr;
  362. }
  363. else
  364. {
  365. m_playing = false;
  366. return E_NOT_VALID_STATE;
  367. }
  368. }
  369. HRESULT Shutdown()
  370. {
  371. if (m_engine)
  372. {
  373. HRESULT hr = m_engine->Shutdown();
  374. if (m_playing && SUCCEEDED(hr)) m_playing = false;
  375. return hr;
  376. }
  377. else
  378. {
  379. m_playing = false;
  380. return E_NOT_VALID_STATE;
  381. }
  382. }
  383. bool IsPlaying() const
  384. {
  385. return m_playing;
  386. }
  387. // ***************************************************************************************
  388. // IUnknown
  389. // ***************************************************************************************
  390. virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
  391. {
  392. HRESULT hr = S_OK;
  393. if (!ppvObject) return E_POINTER;
  394. if (riid == IID_IMFMediaEngineNotify)
  395. *ppvObject = static_cast<IMFMediaEngineNotify*>(this);
  396. else if (riid == IID_IMFMediaEngineExtension)
  397. *ppvObject = static_cast<IMFMediaEngineExtension*>(this);
  398. else
  399. hr = CUnknown::QueryInterface(riid, ppvObject);
  400. return hr;
  401. }
  402. virtual ULONG STDMETHODCALLTYPE AddRef(void)
  403. {
  404. return 1;
  405. }
  406. virtual ULONG STDMETHODCALLTYPE Release(void)
  407. {
  408. return 1;
  409. }
  410. // ***************************************************************************************
  411. // IMFMediaEngineNotify
  412. // ***************************************************************************************
  413. virtual HRESULT STDMETHODCALLTYPE EventNotify(DWORD event, DWORD_PTR param1, DWORD param2)
  414. {
  415. EnterCriticalSection(&m_cs);
  416. if (event == MF_MEDIA_ENGINE_EVENT_ERROR)
  417. {
  418. ComPtr<IMFMediaError> error;
  419. m_engine->GetError(&error);
  420. m_events.push_back(Event(event, error->GetErrorCode(), error->GetExtendedErrorCode()));
  421. }
  422. else
  423. {
  424. m_events.push_back(Event(event, param1, param2));
  425. }
  426. LeaveCriticalSection(&m_cs);
  427. SetEvent(m_event);
  428. return S_OK;
  429. }
  430. // ***************************************************************************************
  431. // IMFMediaEngineExtension
  432. // ***************************************************************************************
  433. virtual HRESULT STDMETHODCALLTYPE CanPlayType(
  434. _In_ BOOL AudioOnly,
  435. _In_ BSTR MimeType,
  436. _Out_ MF_MEDIA_ENGINE_CANPLAY *pAnswer)
  437. {
  438. if (!pAnswer) return E_POINTER;
  439. if (_wcsicmp(MimeType, L"video/avi") == 0)
  440. *pAnswer = MF_MEDIA_ENGINE_CANPLAY_PROBABLY;
  441. else
  442. *pAnswer = MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED;
  443. return S_OK;
  444. }
  445. virtual HRESULT STDMETHODCALLTYPE BeginCreateObject(
  446. _In_ BSTR bstrURL,
  447. _In_opt_ IMFByteStream *pByteStream,
  448. _In_ MF_OBJECT_TYPE type,
  449. _Outptr_ IUnknown **ppIUnknownCancelCookie,
  450. _In_ IMFAsyncCallback *pCallback,
  451. _In_opt_ IUnknown *punkState)
  452. {
  453. if (!pCallback) return E_POINTER;
  454. switch (type)
  455. {
  456. case MF_OBJECT_BYTESTREAM:
  457. if (!bstrURL) return E_POINTER;
  458. break;
  459. case MF_OBJECT_MEDIASOURCE:
  460. if (!bstrURL && !pByteStream) return E_POINTER;
  461. break;
  462. default:
  463. return E_INVALIDARG;
  464. }
  465. ComPtr<IMFAsyncCallback> pcb;
  466. if (type == MF_OBJECT_BYTESTREAM)
  467. {
  468. if (!pByteStream)
  469. {
  470. pcb.Attach(new CMFAsyncCallback(bstrURL, punkState, pCallback));
  471. }
  472. else
  473. {
  474. return E_INVALIDARG;
  475. }
  476. }
  477. if (type == MF_OBJECT_MEDIASOURCE)
  478. {
  479. if (!pByteStream)
  480. {
  481. pcb.Attach(new CMFAsyncCallback(bstrURL, m_resolver.Get(), punkState, pCallback));
  482. }
  483. else
  484. {
  485. pcb.Attach(new CMFAsyncCallback(pByteStream, m_resolver.Get(), punkState, pCallback));
  486. }
  487. }
  488. return MFPutWorkItem2(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, pcb.Get(), nullptr);
  489. }
  490. virtual HRESULT STDMETHODCALLTYPE CancelObjectCreation(
  491. _In_ IUnknown *pIUnknownCancelCookie)
  492. {
  493. return S_OK;
  494. }
  495. virtual HRESULT STDMETHODCALLTYPE EndCreateObject(
  496. _In_ IMFAsyncResult *pResult,
  497. _Outptr_ IUnknown **ppObject)
  498. {
  499. return pResult->GetObject(ppObject);
  500. }
  501. private:
  502. HANDLE m_event;
  503. CRITICAL_SECTION m_cs;
  504. ComPtr<ID3D11Device> m_device;
  505. ComPtr<ID3D11DeviceContext> m_context;
  506. ComPtr<IMFDXGIDeviceManager> m_manager;
  507. ComPtr<IMFMediaEngine> m_engine;
  508. ComPtr<IMFSourceResolver> m_resolver;
  509. ComPtr<ID3D11Texture2D> m_render, m_reader;
  510. std::list<Event> m_events;
  511. bool m_playing;
  512. #undef FAIL_RETURN
  513. };
  514. extern "C" BOOL PAL_PlayAVI_Native(const char *lpszPath)
  515. {
  516. CAVIPlayer player;
  517. HRESULT hr;
  518. DWORD cx, cy;
  519. if (FAILED(hr = player.Initialize())) return FALSE;
  520. if (FAILED(hr = player.Load(ConvertString(lpszPath), &cx, &cy))) return FALSE;
  521. PAL_ClearKeyState();
  522. SDL_Surface* surface = SDL_CreateRGBSurface(SDL_SWSURFACE, cx, cy, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
  523. hr = player.Play([surface](const void* data, UINT pitch)->bool {
  524. if (pitch == surface->pitch)
  525. {
  526. memcpy(surface->pixels, data, surface->pitch * surface->h);
  527. }
  528. else
  529. {
  530. auto dst = (uint8_t*)surface->pixels - surface->pitch, src = (uint8_t*)data - pitch;
  531. auto length = surface->w << 2;
  532. for (int i = 0; i < surface->h; i++, memcpy(dst += surface->pitch, src += pitch, length));
  533. }
  534. VIDEO_DrawSurfaceToScreen(surface);
  535. UTIL_Delay(1);
  536. return (g_InputState.dwKeyPress & (kKeyMenu | kKeySearch)) != 0;
  537. });
  538. SDL_FreeSurface(surface);
  539. if (FAILED(hr)) return FALSE;
  540. if (hr == S_FALSE)
  541. {
  542. //
  543. // Simulate a short delay (like the original game)
  544. //
  545. UTIL_Delay(500);
  546. }
  547. hr = player.Pause();
  548. hr = player.Shutdown();
  549. return TRUE;
  550. }