native_avi.cpp 16 KB

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