media_foundation_renderer.cc 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "media/renderers/win/media_foundation_renderer.h"
  5. #include <Audioclient.h>
  6. #include <mferror.h>
  7. #include <memory>
  8. #include <string>
  9. #include <tuple>
  10. #include "base/callback_helpers.h"
  11. #include "base/guid.h"
  12. #include "base/metrics/histogram_functions.h"
  13. #include "base/numerics/clamped_math.h"
  14. #include "base/numerics/safe_conversions.h"
  15. #include "base/process/process_handle.h"
  16. #include "base/strings/string_number_conversions.h"
  17. #include "base/strings/utf_string_conversions.h"
  18. #include "base/win/scoped_bstr.h"
  19. #include "base/win/scoped_hdc.h"
  20. #include "base/win/scoped_propvariant.h"
  21. #include "base/win/windows_version.h"
  22. #include "base/win/wrapped_window_proc.h"
  23. #include "media/base/bind_to_current_loop.h"
  24. #include "media/base/cdm_context.h"
  25. #include "media/base/media_log.h"
  26. #include "media/base/media_switches.h"
  27. #include "media/base/timestamp_constants.h"
  28. #include "media/base/win/dxgi_device_manager.h"
  29. #include "media/base/win/mf_helpers.h"
  30. #include "media/base/win/mf_initializer.h"
  31. namespace media {
  32. using Microsoft::WRL::ComPtr;
  33. using Microsoft::WRL::MakeAndInitialize;
  34. namespace {
  35. ATOM g_video_window_class = 0;
  36. // The |g_video_window_class| atom obtained is used as the |lpClassName|
  37. // parameter in CreateWindowEx().
  38. // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa
  39. //
  40. // To enable OPM
  41. // (https://docs.microsoft.com/en-us/windows/win32/medfound/output-protection-manager)
  42. // protection for video playback, We call CreateWindowEx() to get a window
  43. // and pass it to MFMediaEngine as an attribute.
  44. bool InitializeVideoWindowClass() {
  45. if (g_video_window_class)
  46. return true;
  47. WNDCLASSEX intermediate_class;
  48. base::win::InitializeWindowClass(
  49. L"VirtualMediaFoundationCdmVideoWindow",
  50. &base::win::WrappedWindowProc<::DefWindowProc>, CS_OWNDC, 0, 0, nullptr,
  51. reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)), nullptr, nullptr,
  52. nullptr, &intermediate_class);
  53. g_video_window_class = RegisterClassEx(&intermediate_class);
  54. if (!g_video_window_class) {
  55. HRESULT register_class_error = HRESULT_FROM_WIN32(GetLastError());
  56. DLOG(ERROR) << "RegisterClass failed: " << PrintHr(register_class_error);
  57. return false;
  58. }
  59. return true;
  60. }
  61. const std::string GetErrorReasonString(
  62. const MediaFoundationRenderer::ErrorReason& reason) {
  63. #define STRINGIFY(value) \
  64. case MediaFoundationRenderer::ErrorReason::value: \
  65. return #value
  66. switch (reason) {
  67. STRINGIFY(kUnknown);
  68. STRINGIFY(kCdmProxyReceivedInInvalidState);
  69. STRINGIFY(kFailedToSetSourceOnMediaEngine);
  70. STRINGIFY(kFailedToSetCurrentTime);
  71. STRINGIFY(kFailedToPlay);
  72. STRINGIFY(kOnPlaybackError);
  73. STRINGIFY(kOnDCompSurfaceHandleSetError);
  74. STRINGIFY(kOnConnectionError);
  75. STRINGIFY(kFailedToSetDCompMode);
  76. STRINGIFY(kFailedToGetDCompSurface);
  77. STRINGIFY(kFailedToDuplicateHandle);
  78. STRINGIFY(kFailedToCreateMediaEngine);
  79. }
  80. #undef STRINGIFY
  81. }
  82. // INVALID_HANDLE_VALUE is the official invalid handle value. Historically, 0 is
  83. // not used as a handle value too.
  84. bool IsInvalidHandle(const HANDLE& handle) {
  85. return handle == INVALID_HANDLE_VALUE || handle == nullptr;
  86. }
  87. } // namespace
  88. // static
  89. void MediaFoundationRenderer::ReportErrorReason(ErrorReason reason) {
  90. base::UmaHistogramEnumeration("Media.MediaFoundationRenderer.ErrorReason",
  91. reason);
  92. }
  93. // static
  94. bool MediaFoundationRenderer::IsSupported() {
  95. return base::win::GetVersion() >= base::win::Version::WIN10;
  96. }
  97. MediaFoundationRenderer::MediaFoundationRenderer(
  98. scoped_refptr<base::SequencedTaskRunner> task_runner,
  99. std::unique_ptr<MediaLog> media_log,
  100. LUID gpu_process_adapter_luid,
  101. bool force_dcomp_mode_for_testing)
  102. : task_runner_(std::move(task_runner)),
  103. media_log_(std::move(media_log)),
  104. gpu_process_adapter_luid_(gpu_process_adapter_luid),
  105. force_dcomp_mode_for_testing_(force_dcomp_mode_for_testing) {
  106. DVLOG_FUNC(1);
  107. }
  108. MediaFoundationRenderer::~MediaFoundationRenderer() {
  109. DVLOG_FUNC(1);
  110. // Perform shutdown/cleanup in the order (shutdown/detach/destroy) we wanted
  111. // without depending on the order of destructors being invoked. We also need
  112. // to invoke MFShutdown() after shutdown/cleanup of MF related objects.
  113. StopSendingStatistics();
  114. if (mf_media_engine_extension_)
  115. mf_media_engine_extension_->Shutdown();
  116. if (mf_media_engine_notify_)
  117. mf_media_engine_notify_->Shutdown();
  118. if (mf_media_engine_)
  119. mf_media_engine_->Shutdown();
  120. if (mf_source_)
  121. mf_source_->DetachResource();
  122. if (dxgi_device_manager_) {
  123. dxgi_device_manager_.Reset();
  124. MFUnlockDXGIDeviceManager();
  125. }
  126. if (virtual_video_window_)
  127. DestroyWindow(virtual_video_window_);
  128. }
  129. void MediaFoundationRenderer::Initialize(MediaResource* media_resource,
  130. RendererClient* client,
  131. PipelineStatusCallback init_cb) {
  132. DVLOG_FUNC(1);
  133. renderer_client_ = client;
  134. // Check the rendering strategy & whether we're operating on clear or
  135. // protected content to determine the starting 'rendering_mode_'.
  136. // If the Direct Composition strategy is specified or if we're operating on
  137. // protected content then start in Direct Composition mode, else start in
  138. // Frame Server mode. This behavior must match the logic in
  139. // MediaFoundationRendererClient::Initialize.
  140. auto rendering_strategy = kMediaFoundationClearRenderingStrategyParam.Get();
  141. rendering_mode_ =
  142. rendering_strategy ==
  143. MediaFoundationClearRenderingStrategy::kDirectComposition
  144. ? MediaFoundationRenderingMode::DirectComposition
  145. : MediaFoundationRenderingMode::FrameServer;
  146. for (DemuxerStream* stream : media_resource->GetAllStreams()) {
  147. if (stream->type() == DemuxerStream::Type::VIDEO &&
  148. stream->video_decoder_config().is_encrypted()) {
  149. // This is protected content which only supports Direct Composition mode,
  150. // update 'rendering_mode_' accordingly.
  151. rendering_mode_ = MediaFoundationRenderingMode::DirectComposition;
  152. }
  153. }
  154. MEDIA_LOG(INFO, media_log_)
  155. << "Starting MediaFoundationRenderingMode: " << rendering_mode_;
  156. HRESULT hr = CreateMediaEngine(media_resource);
  157. if (FAILED(hr)) {
  158. DLOG(ERROR) << "Failed to create media engine: " << PrintHr(hr);
  159. base::UmaHistogramSparse(
  160. "Media.MediaFoundationRenderer.CreateMediaEngineError", hr);
  161. OnError(PIPELINE_ERROR_INITIALIZATION_FAILED,
  162. ErrorReason::kFailedToCreateMediaEngine, hr, std::move(init_cb));
  163. return;
  164. }
  165. SetVolume(volume_);
  166. std::move(init_cb).Run(PIPELINE_OK);
  167. }
  168. HRESULT MediaFoundationRenderer::CreateMediaEngine(
  169. MediaResource* media_resource) {
  170. DVLOG_FUNC(1);
  171. if (!InitializeMediaFoundation())
  172. return E_FAIL;
  173. // Set `cdm_proxy_` early on so errors can be reported via the CDM for better
  174. // error aggregation. See `CdmDocumentServiceImpl::OnCdmEvent()`.
  175. if (cdm_context_) {
  176. cdm_proxy_ = cdm_context_->GetMediaFoundationCdmProxy();
  177. if (!cdm_proxy_) {
  178. DLOG(ERROR) << __func__ << ": CDM does not support MF CDM interface";
  179. return MF_E_UNEXPECTED;
  180. }
  181. }
  182. // TODO(frankli): Only call the followings when there is a video stream.
  183. RETURN_IF_FAILED(InitializeDXGIDeviceManager());
  184. RETURN_IF_FAILED(InitializeVirtualVideoWindow());
  185. // The OnXxx() callbacks are invoked by MF threadpool thread, we would like
  186. // to bind the callbacks to |task_runner_| MessgaeLoop.
  187. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  188. auto weak_this = weak_factory_.GetWeakPtr();
  189. RETURN_IF_FAILED(MakeAndInitialize<MediaEngineNotifyImpl>(
  190. &mf_media_engine_notify_,
  191. BindToCurrentLoop(base::BindRepeating(
  192. &MediaFoundationRenderer::OnPlaybackError, weak_this)),
  193. BindToCurrentLoop(base::BindRepeating(
  194. &MediaFoundationRenderer::OnPlaybackEnded, weak_this)),
  195. BindToCurrentLoop(base::BindRepeating(
  196. &MediaFoundationRenderer::OnFormatChange, weak_this)),
  197. BindToCurrentLoop(base::BindRepeating(
  198. &MediaFoundationRenderer::OnLoadedData, weak_this)),
  199. BindToCurrentLoop(
  200. base::BindRepeating(&MediaFoundationRenderer::OnPlaying, weak_this)),
  201. BindToCurrentLoop(
  202. base::BindRepeating(&MediaFoundationRenderer::OnWaiting, weak_this)),
  203. BindToCurrentLoop(base::BindRepeating(
  204. &MediaFoundationRenderer::OnTimeUpdate, weak_this))));
  205. ComPtr<IMFAttributes> creation_attributes;
  206. RETURN_IF_FAILED(MFCreateAttributes(&creation_attributes, 6));
  207. RETURN_IF_FAILED(creation_attributes->SetUnknown(
  208. MF_MEDIA_ENGINE_CALLBACK, mf_media_engine_notify_.Get()));
  209. RETURN_IF_FAILED(
  210. creation_attributes->SetUINT32(MF_MEDIA_ENGINE_CONTENT_PROTECTION_FLAGS,
  211. MF_MEDIA_ENGINE_ENABLE_PROTECTED_CONTENT));
  212. RETURN_IF_FAILED(creation_attributes->SetUINT32(
  213. MF_MEDIA_ENGINE_AUDIO_CATEGORY, AudioCategory_Media));
  214. if (virtual_video_window_) {
  215. RETURN_IF_FAILED(creation_attributes->SetUINT64(
  216. MF_MEDIA_ENGINE_OPM_HWND,
  217. reinterpret_cast<uint64_t>(virtual_video_window_)));
  218. }
  219. if (dxgi_device_manager_) {
  220. RETURN_IF_FAILED(creation_attributes->SetUnknown(
  221. MF_MEDIA_ENGINE_DXGI_MANAGER, dxgi_device_manager_.Get()));
  222. // TODO(crbug.com/1276067): We'll investigate scenarios to see if we can use
  223. // the on-screen video window size and not the native video size.
  224. if (rendering_mode_ == MediaFoundationRenderingMode::FrameServer) {
  225. gfx::Size max_video_size;
  226. bool has_video = false;
  227. for (auto* stream : media_resource->GetAllStreams()) {
  228. if (stream->type() == media::DemuxerStream::VIDEO) {
  229. has_video = true;
  230. gfx::Size video_size = stream->video_decoder_config().natural_size();
  231. if (video_size.height() > max_video_size.height()) {
  232. max_video_size.set_height(video_size.height());
  233. }
  234. if (video_size.width() > max_video_size.width()) {
  235. max_video_size.set_width(video_size.width());
  236. }
  237. }
  238. }
  239. if (has_video) {
  240. RETURN_IF_FAILED(InitializeTexturePool(max_video_size));
  241. }
  242. }
  243. }
  244. RETURN_IF_FAILED(
  245. MakeAndInitialize<MediaEngineExtension>(&mf_media_engine_extension_));
  246. RETURN_IF_FAILED(creation_attributes->SetUnknown(
  247. MF_MEDIA_ENGINE_EXTENSION, mf_media_engine_extension_.Get()));
  248. ComPtr<IMFMediaEngineClassFactory> class_factory;
  249. RETURN_IF_FAILED(CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr,
  250. CLSCTX_INPROC_SERVER,
  251. IID_PPV_ARGS(&class_factory)));
  252. // TODO(frankli): Use MF_MEDIA_ENGINE_REAL_TIME_MODE for low latency hint
  253. // instead of 0.
  254. RETURN_IF_FAILED(class_factory->CreateInstance(0, creation_attributes.Get(),
  255. &mf_media_engine_));
  256. auto media_resource_type_ = media_resource->GetType();
  257. if (media_resource_type_ != MediaResource::Type::STREAM) {
  258. DLOG(ERROR) << "MediaResource is not of STREAM";
  259. return E_INVALIDARG;
  260. }
  261. RETURN_IF_FAILED(MakeAndInitialize<MediaFoundationSourceWrapper>(
  262. &mf_source_, media_resource, media_log_.get(), task_runner_));
  263. if (force_dcomp_mode_for_testing_)
  264. std::ignore = SetDCompModeInternal();
  265. if (!mf_source_->HasEncryptedStream()) {
  266. // Supports clear stream for testing.
  267. return SetSourceOnMediaEngine();
  268. }
  269. // Has encrypted stream.
  270. RETURN_IF_FAILED(MakeAndInitialize<MediaFoundationProtectionManager>(
  271. &content_protection_manager_, task_runner_,
  272. base::BindRepeating(&MediaFoundationRenderer::OnProtectionManagerWaiting,
  273. weak_factory_.GetWeakPtr())));
  274. ComPtr<IMFMediaEngineProtectedContent> protected_media_engine;
  275. RETURN_IF_FAILED(mf_media_engine_.As(&protected_media_engine));
  276. RETURN_IF_FAILED(protected_media_engine->SetContentProtectionManager(
  277. content_protection_manager_.Get()));
  278. waiting_for_mf_cdm_ = true;
  279. if (!cdm_context_) {
  280. DCHECK(!cdm_proxy_);
  281. return S_OK;
  282. }
  283. DCHECK(cdm_proxy_);
  284. OnCdmProxyReceived();
  285. return S_OK;
  286. }
  287. HRESULT MediaFoundationRenderer::SetSourceOnMediaEngine() {
  288. DVLOG_FUNC(1);
  289. if (!mf_source_) {
  290. LOG(ERROR) << "mf_source_ is null.";
  291. return HRESULT_FROM_WIN32(ERROR_INVALID_STATE);
  292. }
  293. ComPtr<IUnknown> source_unknown;
  294. RETURN_IF_FAILED(mf_source_.As(&source_unknown));
  295. RETURN_IF_FAILED(
  296. mf_media_engine_extension_->SetMediaSource(source_unknown.Get()));
  297. DVLOG(2) << "Set MFRendererSrc scheme as the source for MFMediaEngine.";
  298. base::win::ScopedBstr mf_renderer_source_scheme(
  299. base::ASCIIToWide("MFRendererSrc"));
  300. // We need to set our source scheme first in order for the MFMediaEngine to
  301. // load of our custom MFMediaSource.
  302. RETURN_IF_FAILED(
  303. mf_media_engine_->SetSource(mf_renderer_source_scheme.Get()));
  304. return S_OK;
  305. }
  306. HRESULT MediaFoundationRenderer::InitializeDXGIDeviceManager() {
  307. UINT device_reset_token;
  308. RETURN_IF_FAILED(
  309. MFLockDXGIDeviceManager(&device_reset_token, &dxgi_device_manager_));
  310. ComPtr<ID3D11Device> d3d11_device;
  311. UINT creation_flags =
  312. (D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT |
  313. D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS);
  314. static const D3D_FEATURE_LEVEL feature_levels[] = {
  315. D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
  316. D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2,
  317. D3D_FEATURE_LEVEL_9_1};
  318. Microsoft::WRL::ComPtr<IDXGIFactory1> factory;
  319. RETURN_IF_FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory)));
  320. Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_to_use;
  321. if (gpu_process_adapter_luid_.LowPart || gpu_process_adapter_luid_.HighPart) {
  322. Microsoft::WRL::ComPtr<IDXGIAdapter> temp_adapter;
  323. for (UINT i = 0; SUCCEEDED(factory->EnumAdapters(i, &temp_adapter)); i++) {
  324. DXGI_ADAPTER_DESC desc;
  325. RETURN_IF_FAILED(temp_adapter->GetDesc(&desc));
  326. if (desc.AdapterLuid.LowPart == gpu_process_adapter_luid_.LowPart &&
  327. desc.AdapterLuid.HighPart == gpu_process_adapter_luid_.HighPart) {
  328. adapter_to_use = std::move(temp_adapter);
  329. break;
  330. }
  331. }
  332. }
  333. RETURN_IF_FAILED(D3D11CreateDevice(
  334. adapter_to_use.Get(),
  335. adapter_to_use ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, 0,
  336. creation_flags, feature_levels, std::size(feature_levels),
  337. D3D11_SDK_VERSION, &d3d11_device, nullptr, nullptr));
  338. RETURN_IF_FAILED(media::SetDebugName(d3d11_device.Get(), "Media_MFRenderer"));
  339. ComPtr<ID3D10Multithread> multithreaded_device;
  340. RETURN_IF_FAILED(d3d11_device.As(&multithreaded_device));
  341. multithreaded_device->SetMultithreadProtected(TRUE);
  342. return dxgi_device_manager_->ResetDevice(d3d11_device.Get(),
  343. device_reset_token);
  344. }
  345. HRESULT MediaFoundationRenderer::InitializeVirtualVideoWindow() {
  346. if (!InitializeVideoWindowClass())
  347. return E_FAIL;
  348. virtual_video_window_ =
  349. CreateWindowEx(WS_EX_NOPARENTNOTIFY | WS_EX_LAYERED | WS_EX_TRANSPARENT |
  350. WS_EX_NOREDIRECTIONBITMAP,
  351. reinterpret_cast<wchar_t*>(g_video_window_class), L"",
  352. WS_POPUP | WS_DISABLED | WS_CLIPSIBLINGS, 0, 0, 1, 1,
  353. nullptr, nullptr, nullptr, nullptr);
  354. if (!virtual_video_window_) {
  355. HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
  356. DLOG(ERROR) << "Failed to create virtual window: " << PrintHr(hr);
  357. return hr;
  358. }
  359. return S_OK;
  360. }
  361. void MediaFoundationRenderer::SetCdm(CdmContext* cdm_context,
  362. CdmAttachedCB cdm_attached_cb) {
  363. DVLOG_FUNC(1);
  364. if (cdm_context_ || !cdm_context) {
  365. DLOG(ERROR) << "Failed in checking CdmContext.";
  366. std::move(cdm_attached_cb).Run(false);
  367. return;
  368. }
  369. cdm_context_ = cdm_context;
  370. if (waiting_for_mf_cdm_) {
  371. cdm_proxy_ = cdm_context_->GetMediaFoundationCdmProxy();
  372. if (!cdm_proxy_) {
  373. DLOG(ERROR) << "CDM does not support MediaFoundationCdmProxy";
  374. std::move(cdm_attached_cb).Run(false);
  375. return;
  376. }
  377. OnCdmProxyReceived();
  378. }
  379. std::move(cdm_attached_cb).Run(true);
  380. }
  381. void MediaFoundationRenderer::SetLatencyHint(
  382. absl::optional<base::TimeDelta> /*latency_hint*/) {
  383. // TODO(frankli): Ensure MFMediaEngine rendering pipeine is in real time mode.
  384. NOTIMPLEMENTED() << "We do not use the latency hint today";
  385. }
  386. void MediaFoundationRenderer::OnCdmProxyReceived() {
  387. DVLOG_FUNC(1);
  388. DCHECK(cdm_proxy_);
  389. if (!waiting_for_mf_cdm_ || !content_protection_manager_) {
  390. OnError(PIPELINE_ERROR_INVALID_STATE,
  391. ErrorReason::kCdmProxyReceivedInInvalidState);
  392. return;
  393. }
  394. waiting_for_mf_cdm_ = false;
  395. content_protection_manager_->SetCdmProxy(cdm_proxy_);
  396. mf_source_->SetCdmProxy(cdm_proxy_);
  397. HRESULT hr = SetSourceOnMediaEngine();
  398. if (FAILED(hr)) {
  399. OnError(PIPELINE_ERROR_COULD_NOT_RENDER,
  400. ErrorReason::kFailedToSetSourceOnMediaEngine, hr);
  401. return;
  402. }
  403. }
  404. void MediaFoundationRenderer::Flush(base::OnceClosure flush_cb) {
  405. DVLOG_FUNC(2);
  406. HRESULT hr = PauseInternal();
  407. // Ignore any Pause() error. We can continue to flush |mf_source_| instead of
  408. // stopping the playback with error.
  409. DVLOG_IF(1, FAILED(hr)) << "Failed to pause playback on flush: "
  410. << PrintHr(hr);
  411. mf_source_->FlushStreams();
  412. std::move(flush_cb).Run();
  413. }
  414. void MediaFoundationRenderer::SetMediaFoundationRenderingMode(
  415. MediaFoundationRenderingMode render_mode) {
  416. ComPtr<IMFMediaEngineEx> mf_media_engine_ex;
  417. HRESULT hr = mf_media_engine_.As(&mf_media_engine_ex);
  418. if (mf_media_engine_->HasVideo()) {
  419. if (render_mode == MediaFoundationRenderingMode::FrameServer) {
  420. // Make sure we reinitialize the texture pool
  421. hr = InitializeTexturePool(native_video_size_);
  422. } else if (render_mode == MediaFoundationRenderingMode::DirectComposition) {
  423. // If needed renegotiate the DComp visual and send it to the client for
  424. // presentation
  425. } else {
  426. DVLOG(1) << "Rendering mode: " << static_cast<int>(render_mode)
  427. << " is unsupported";
  428. MEDIA_LOG(ERROR, media_log_)
  429. << "MediaFoundationRenderer SetMediaFoundationRenderingMode: "
  430. << (int)render_mode
  431. << " is not defined. No change to the rendering mode.";
  432. hr = E_NOT_SET;
  433. }
  434. if (SUCCEEDED(hr)) {
  435. hr = mf_media_engine_ex->EnableWindowlessSwapchainMode(
  436. render_mode == MediaFoundationRenderingMode::DirectComposition);
  437. if (SUCCEEDED(hr)) {
  438. rendering_mode_ = render_mode;
  439. MEDIA_LOG(INFO, media_log_)
  440. << "Set MediaFoundationRenderingMode: " << rendering_mode_;
  441. }
  442. }
  443. }
  444. }
  445. bool MediaFoundationRenderer::InFrameServerMode() {
  446. return rendering_mode_ == MediaFoundationRenderingMode::FrameServer;
  447. }
  448. void MediaFoundationRenderer::StartPlayingFrom(base::TimeDelta time) {
  449. double current_time = time.InSecondsF();
  450. DVLOG_FUNC(2) << "current_time=" << current_time;
  451. // Note: It is okay for |waiting_for_mf_cdm_| to be true here. The
  452. // MFMediaEngine supports calls to Play/SetCurrentTime before a source is set
  453. // (it will apply the relevant changes to the playback state once a source is
  454. // set on it).
  455. // SetCurrentTime() completes asynchronously. When the seek operation starts,
  456. // the MFMediaEngine sends an MF_MEDIA_ENGINE_EVENT_SEEKING event. When the
  457. // seek operation completes, the MFMediaEngine sends an
  458. // MF_MEDIA_ENGINE_EVENT_SEEKED event.
  459. HRESULT hr = mf_media_engine_->SetCurrentTime(current_time);
  460. if (FAILED(hr)) {
  461. OnError(PIPELINE_ERROR_COULD_NOT_RENDER,
  462. ErrorReason::kFailedToSetCurrentTime, hr);
  463. return;
  464. }
  465. hr = mf_media_engine_->Play();
  466. if (FAILED(hr)) {
  467. OnError(PIPELINE_ERROR_COULD_NOT_RENDER, ErrorReason::kFailedToPlay, hr);
  468. return;
  469. }
  470. }
  471. void MediaFoundationRenderer::SetPlaybackRate(double playback_rate) {
  472. DVLOG_FUNC(2) << "playback_rate=" << playback_rate;
  473. HRESULT hr = mf_media_engine_->SetPlaybackRate(playback_rate);
  474. // Ignore error so that the media continues to play rather than stopped.
  475. DVLOG_IF(1, FAILED(hr)) << "Failed to set playback rate: " << PrintHr(hr);
  476. }
  477. void MediaFoundationRenderer::GetDCompSurface(GetDCompSurfaceCB callback) {
  478. DVLOG_FUNC(1);
  479. HRESULT hr = SetDCompModeInternal();
  480. if (FAILED(hr)) {
  481. OnError(PIPELINE_ERROR_COULD_NOT_RENDER, ErrorReason::kFailedToSetDCompMode,
  482. hr);
  483. std::move(callback).Run(base::win::ScopedHandle(), PrintHr(hr));
  484. return;
  485. }
  486. HANDLE surface_handle = INVALID_HANDLE_VALUE;
  487. hr = GetDCompSurfaceInternal(&surface_handle);
  488. // The handle could still be invalid after a non failure (e.g. S_FALSE) is
  489. // returned. See https://crbug.com/1307065.
  490. if (FAILED(hr) || IsInvalidHandle(surface_handle)) {
  491. OnError(PIPELINE_ERROR_COULD_NOT_RENDER,
  492. ErrorReason::kFailedToGetDCompSurface, hr);
  493. std::move(callback).Run(base::win::ScopedHandle(), PrintHr(hr));
  494. return;
  495. }
  496. // Only need read & execute access right for the handle to be duplicated
  497. // without breaking in sandbox_win.cc!CheckDuplicateHandle().
  498. const base::ProcessHandle process = ::GetCurrentProcess();
  499. HANDLE duplicated_handle = INVALID_HANDLE_VALUE;
  500. const BOOL result = ::DuplicateHandle(
  501. process, surface_handle, process, &duplicated_handle,
  502. GENERIC_READ | GENERIC_EXECUTE, false, DUPLICATE_CLOSE_SOURCE);
  503. if (!result || IsInvalidHandle(surface_handle)) {
  504. hr = ::GetLastError();
  505. OnError(PIPELINE_ERROR_COULD_NOT_RENDER,
  506. ErrorReason::kFailedToDuplicateHandle, hr);
  507. std::move(callback).Run(base::win::ScopedHandle(), PrintHr(hr));
  508. return;
  509. }
  510. std::move(callback).Run(base::win::ScopedHandle(duplicated_handle), "");
  511. }
  512. // TODO(crbug.com/1070030): Investigate if we need to add
  513. // OnSelectedVideoTracksChanged() to media renderer.mojom.
  514. void MediaFoundationRenderer::SetVideoStreamEnabled(bool enabled) {
  515. DVLOG_FUNC(1) << "enabled=" << enabled;
  516. if (!mf_source_)
  517. return;
  518. const bool needs_restart = mf_source_->SetVideoStreamEnabled(enabled);
  519. if (needs_restart) {
  520. // If the media source indicates that we need to restart playback (e.g due
  521. // to a newly enabled stream being EOS), queue a pause and play operation.
  522. PauseInternal();
  523. mf_media_engine_->Play();
  524. }
  525. }
  526. void MediaFoundationRenderer::SetOutputRect(const gfx::Rect& output_rect,
  527. SetOutputRectCB callback) {
  528. DVLOG_FUNC(2);
  529. if (virtual_video_window_ &&
  530. !::SetWindowPos(virtual_video_window_, HWND_BOTTOM, output_rect.x(),
  531. output_rect.y(), output_rect.width(),
  532. output_rect.height(), SWP_NOACTIVATE)) {
  533. DLOG(ERROR) << "Failed to SetWindowPos: "
  534. << PrintHr(HRESULT_FROM_WIN32(GetLastError()));
  535. std::move(callback).Run(false);
  536. return;
  537. }
  538. if (FAILED(UpdateVideoStream(output_rect))) {
  539. std::move(callback).Run(false);
  540. return;
  541. }
  542. std::move(callback).Run(true);
  543. }
  544. HRESULT MediaFoundationRenderer::InitializeTexturePool(const gfx::Size& size) {
  545. DXGIDeviceScopedHandle dxgi_device_handle(dxgi_device_manager_.Get());
  546. ComPtr<ID3D11Device> d3d11_device = dxgi_device_handle.GetDevice();
  547. if (d3d11_device.Get() == nullptr) {
  548. return E_UNEXPECTED;
  549. }
  550. // TODO(crbug.com/1276067): change |size| to instead use the required
  551. // size of the output (for example if the video is only 1280x720 instead
  552. // of a source frame of 1920x1080 we'd use the 1280x720 texture size).
  553. // However we also need to investigate the scenario of WebGL and 360 video
  554. // where they need the original frame size instead of the window size due
  555. // to later image processing.
  556. RETURN_IF_FAILED(texture_pool_.Initialize(d3d11_device.Get(),
  557. initialized_frame_pool_cb_, size));
  558. return S_OK;
  559. }
  560. HRESULT MediaFoundationRenderer::UpdateVideoStream(const gfx::Rect& rect) {
  561. ComPtr<IMFMediaEngineEx> mf_media_engine_ex;
  562. RETURN_IF_FAILED(mf_media_engine_.As(&mf_media_engine_ex));
  563. RECT dest_rect = {0, 0, rect.width(), rect.height()};
  564. RETURN_IF_FAILED(mf_media_engine_ex->UpdateVideoStream(
  565. /*pSrc=*/nullptr, &dest_rect, /*pBorderClr=*/nullptr));
  566. if (rendering_mode_ == MediaFoundationRenderingMode::FrameServer) {
  567. RETURN_IF_FAILED(InitializeTexturePool(native_video_size_));
  568. }
  569. return S_OK;
  570. }
  571. HRESULT MediaFoundationRenderer::SetDCompModeInternal() {
  572. DVLOG_FUNC(1);
  573. ComPtr<IMFMediaEngineEx> media_engine_ex;
  574. RETURN_IF_FAILED(mf_media_engine_.As(&media_engine_ex));
  575. RETURN_IF_FAILED(media_engine_ex->EnableWindowlessSwapchainMode(true));
  576. return S_OK;
  577. }
  578. HRESULT MediaFoundationRenderer::GetDCompSurfaceInternal(
  579. HANDLE* surface_handle) {
  580. DVLOG_FUNC(1);
  581. ComPtr<IMFMediaEngineEx> media_engine_ex;
  582. RETURN_IF_FAILED(mf_media_engine_.As(&media_engine_ex));
  583. RETURN_IF_FAILED(media_engine_ex->GetVideoSwapchainHandle(surface_handle));
  584. return S_OK;
  585. }
  586. HRESULT MediaFoundationRenderer::PopulateStatistics(
  587. PipelineStatistics& statistics) {
  588. ComPtr<IMFMediaEngineEx> media_engine_ex;
  589. RETURN_IF_FAILED(mf_media_engine_.As(&media_engine_ex));
  590. base::win::ScopedPropVariant frames_rendered;
  591. RETURN_IF_FAILED(media_engine_ex->GetStatistics(
  592. MF_MEDIA_ENGINE_STATISTIC_FRAMES_RENDERED, frames_rendered.Receive()));
  593. base::win::ScopedPropVariant frames_dropped;
  594. RETURN_IF_FAILED(media_engine_ex->GetStatistics(
  595. MF_MEDIA_ENGINE_STATISTIC_FRAMES_DROPPED, frames_dropped.Receive()));
  596. statistics.video_frames_decoded =
  597. frames_rendered.get().ulVal + frames_dropped.get().ulVal;
  598. statistics.video_frames_dropped = frames_dropped.get().ulVal;
  599. DVLOG_FUNC(3) << "video_frames_decoded=" << statistics.video_frames_decoded
  600. << ", video_frames_dropped=" << statistics.video_frames_dropped;
  601. return S_OK;
  602. }
  603. void MediaFoundationRenderer::SendStatistics() {
  604. PipelineStatistics new_stats = {};
  605. HRESULT hr = PopulateStatistics(new_stats);
  606. if (FAILED(hr)) {
  607. LIMITED_MEDIA_LOG(INFO, media_log_, populate_statistics_failure_count_, 3)
  608. << "MediaFoundationRenderer failed to populate stats: " + PrintHr(hr);
  609. return;
  610. }
  611. const int kSignificantPlaybackFrames = 5400; // About 30 fps for 3 minutes.
  612. if (!has_reported_significant_playback_ && cdm_proxy_ &&
  613. new_stats.video_frames_decoded >= kSignificantPlaybackFrames) {
  614. has_reported_significant_playback_ = true;
  615. cdm_proxy_->OnSignificantPlayback();
  616. }
  617. if (statistics_ != new_stats) {
  618. // OnStatisticsUpdate() expects delta values.
  619. PipelineStatistics delta;
  620. delta.video_frames_decoded = base::ClampSub(
  621. new_stats.video_frames_decoded, statistics_.video_frames_decoded);
  622. delta.video_frames_dropped = base::ClampSub(
  623. new_stats.video_frames_dropped, statistics_.video_frames_dropped);
  624. statistics_ = new_stats;
  625. renderer_client_->OnStatisticsUpdate(delta);
  626. }
  627. }
  628. void MediaFoundationRenderer::StartSendingStatistics() {
  629. DVLOG_FUNC(2);
  630. // Clear `statistics_` to reset the base for OnStatisticsUpdate(), this is
  631. // needed since flush will clear the internal stats in MediaFoundation.
  632. statistics_ = PipelineStatistics();
  633. const auto kPipelineStatsPollingPeriod = base::Milliseconds(500);
  634. statistics_timer_.Start(FROM_HERE, kPipelineStatsPollingPeriod, this,
  635. &MediaFoundationRenderer::SendStatistics);
  636. }
  637. void MediaFoundationRenderer::StopSendingStatistics() {
  638. DVLOG_FUNC(2);
  639. statistics_timer_.Stop();
  640. }
  641. void MediaFoundationRenderer::SetVolume(float volume) {
  642. DVLOG_FUNC(2) << "volume=" << volume;
  643. volume_ = volume;
  644. if (!mf_media_engine_)
  645. return;
  646. HRESULT hr = mf_media_engine_->SetVolume(volume_);
  647. DVLOG_IF(1, FAILED(hr)) << "Failed to set volume: " << PrintHr(hr);
  648. }
  649. void MediaFoundationRenderer::SetFrameReturnCallbacks(
  650. FrameReturnCallback frame_available_cb,
  651. FramePoolInitializedCallback initialized_frame_pool_cb) {
  652. frame_available_cb_ = std::move(frame_available_cb);
  653. initialized_frame_pool_cb_ = std::move(initialized_frame_pool_cb);
  654. }
  655. void MediaFoundationRenderer::SetGpuProcessAdapterLuid(
  656. LUID gpu_process_adapter_luid) {
  657. // TODO(wicarr, crbug.com/1342621): When the GPU adapter changes or the GPU
  658. // process is restarted we need to recover our Frame Server or DComp
  659. // textures, otherwise we'll fail to present any video frames to the user.
  660. gpu_process_adapter_luid_ = gpu_process_adapter_luid;
  661. }
  662. base::TimeDelta MediaFoundationRenderer::GetMediaTime() {
  663. // GetCurrentTime is expanded as GetTickCount in base/win/windows_types.h
  664. #undef GetCurrentTime
  665. double current_time = mf_media_engine_->GetCurrentTime();
  666. // Restore macro definition.
  667. #define GetCurrentTime() GetTickCount()
  668. auto media_time = base::Seconds(current_time);
  669. DVLOG_FUNC(3) << "media_time=" << media_time;
  670. return media_time;
  671. }
  672. void MediaFoundationRenderer::OnPlaybackError(PipelineStatus status,
  673. HRESULT hr) {
  674. DVLOG_FUNC(1) << "status=" << status << ", hr=" << hr;
  675. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  676. base::UmaHistogramSparse("Media.MediaFoundationRenderer.PlaybackError", hr);
  677. StopSendingStatistics();
  678. OnError(status, ErrorReason::kOnPlaybackError, hr);
  679. }
  680. void MediaFoundationRenderer::OnPlaybackEnded() {
  681. DVLOG_FUNC(2);
  682. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  683. StopSendingStatistics();
  684. renderer_client_->OnEnded();
  685. }
  686. void MediaFoundationRenderer::OnFormatChange() {
  687. DVLOG_FUNC(3);
  688. OnVideoNaturalSizeChange();
  689. }
  690. void MediaFoundationRenderer::OnLoadedData() {
  691. DVLOG_FUNC(3);
  692. OnVideoNaturalSizeChange();
  693. OnBufferingStateChange(
  694. BufferingState::BUFFERING_HAVE_ENOUGH,
  695. BufferingStateChangeReason::BUFFERING_CHANGE_REASON_UNKNOWN);
  696. }
  697. void MediaFoundationRenderer::OnPlaying() {
  698. DVLOG_FUNC(3);
  699. OnBufferingStateChange(
  700. BufferingState::BUFFERING_HAVE_ENOUGH,
  701. BufferingStateChangeReason::BUFFERING_CHANGE_REASON_UNKNOWN);
  702. // The OnPlaying callback from MediaEngineNotifyImpl lets us know that an
  703. // MF_MEDIA_ENGINE_EVENT_PLAYING message has been received. At this point we
  704. // can safely start sending Statistics as any asynchronous Flush action in
  705. // media engine, which would have reset the engine's statistics, will have
  706. // been completed.
  707. StartSendingStatistics();
  708. }
  709. void MediaFoundationRenderer::OnWaiting() {
  710. OnBufferingStateChange(
  711. BufferingState::BUFFERING_HAVE_NOTHING,
  712. BufferingStateChangeReason::BUFFERING_CHANGE_REASON_UNKNOWN);
  713. }
  714. void MediaFoundationRenderer::OnTimeUpdate() {
  715. DVLOG_FUNC(3);
  716. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  717. }
  718. void MediaFoundationRenderer::OnProtectionManagerWaiting(WaitingReason reason) {
  719. DVLOG_FUNC(2);
  720. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  721. renderer_client_->OnWaiting(reason);
  722. }
  723. void MediaFoundationRenderer::OnBufferingStateChange(
  724. BufferingState state,
  725. BufferingStateChangeReason reason) {
  726. DVLOG_FUNC(2);
  727. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  728. if (state == BufferingState::BUFFERING_HAVE_ENOUGH) {
  729. max_buffering_state_ = state;
  730. }
  731. if (state == BufferingState::BUFFERING_HAVE_NOTHING &&
  732. max_buffering_state_ != BufferingState::BUFFERING_HAVE_ENOUGH) {
  733. // Prevent sending BUFFERING_HAVE_NOTHING if we haven't previously sent a
  734. // BUFFERING_HAVE_ENOUGH state.
  735. return;
  736. }
  737. DVLOG_FUNC(2) << "state=" << state << ", reason=" << reason;
  738. renderer_client_->OnBufferingStateChange(state, reason);
  739. }
  740. HRESULT MediaFoundationRenderer::PauseInternal() {
  741. // Media Engine resets aggregate statistics when it flushes - such as a
  742. // transition to the Pause state & then back to Play state. To try and
  743. // avoid cases where we may get Media Engine's reset statistics call
  744. // StopSendingStatistics before transitioning to Pause.
  745. StopSendingStatistics();
  746. return mf_media_engine_->Pause();
  747. }
  748. void MediaFoundationRenderer::OnVideoNaturalSizeChange() {
  749. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  750. const bool has_video = mf_media_engine_->HasVideo();
  751. DVLOG_FUNC(2) << "has_video=" << has_video;
  752. // Skip if there are no video streams. This can happen because this is
  753. // originated from MF_MEDIA_ENGINE_EVENT_FORMATCHANGE.
  754. if (!has_video)
  755. return;
  756. DWORD native_width;
  757. DWORD native_height;
  758. HRESULT hr =
  759. mf_media_engine_->GetNativeVideoSize(&native_width, &native_height);
  760. if (FAILED(hr)) {
  761. // TODO(xhwang): Add UMA to probe if this can happen.
  762. DLOG(ERROR) << "Failed to get native video size from MediaEngine, using "
  763. "default (640x320). hr="
  764. << hr;
  765. native_video_size_ = {640, 320};
  766. } else {
  767. native_video_size_ = {base::checked_cast<int>(native_width),
  768. base::checked_cast<int>(native_height)};
  769. }
  770. // TODO(frankli): Let test code to call `UpdateVideoStream()`.
  771. if (force_dcomp_mode_for_testing_) {
  772. const gfx::Rect test_rect(/*x=*/0, /*y=*/0, /*width=*/640, /*height=*/320);
  773. // This invokes IMFMediaEngineEx::UpdateVideoStream() for video frames to
  774. // be presented. Otherwise, the Media Foundation video renderer will not
  775. // request video samples from our source.
  776. std::ignore = UpdateVideoStream(test_rect);
  777. }
  778. if (rendering_mode_ == MediaFoundationRenderingMode::FrameServer) {
  779. InitializeTexturePool(native_video_size_);
  780. }
  781. renderer_client_->OnVideoNaturalSizeChange(native_video_size_);
  782. }
  783. void MediaFoundationRenderer::OnError(PipelineStatus status,
  784. ErrorReason reason,
  785. absl::optional<HRESULT> hresult,
  786. PipelineStatusCallback status_cb) {
  787. const std::string error =
  788. "MediaFoundationRenderer error: " + GetErrorReasonString(reason) +
  789. (hresult.has_value() ? (" (" + PrintHr(hresult.value()) + ")") : "");
  790. DLOG(ERROR) << error;
  791. // Report to MediaLog so the error will show up in media internals and
  792. // MediaError.message.
  793. MEDIA_LOG(ERROR, media_log_) << error;
  794. // Report the error to UMA.
  795. ReportErrorReason(reason);
  796. // HRESULT 0x8004CD12 is DRM_E_TEE_INVALID_HWDRM_STATE, which can happen
  797. // during OS sleep/resume, or moving video to different graphics adapters.
  798. // This is not an error, so special case it here.
  799. PipelineStatus new_status = status;
  800. if (hresult.has_value() && hresult == static_cast<HRESULT>(0x8004CD12)) {
  801. new_status = PIPELINE_ERROR_HARDWARE_CONTEXT_RESET;
  802. if (cdm_proxy_)
  803. cdm_proxy_->OnHardwareContextReset();
  804. } else if (cdm_proxy_) {
  805. cdm_proxy_->OnPlaybackError();
  806. }
  807. // Attach hresult to `new_status` for logging and metrics reporting.
  808. if (hresult.has_value())
  809. new_status.WithData("hresult", static_cast<uint32_t>(hresult.value()));
  810. if (status_cb)
  811. std::move(status_cb).Run(new_status);
  812. else
  813. renderer_client_->OnError(new_status);
  814. }
  815. void MediaFoundationRenderer::RequestNextFrameBetweenTimestamps(
  816. base::TimeTicks deadline_min,
  817. base::TimeTicks deadline_max) {
  818. DCHECK(task_runner_->RunsTasksInCurrentSequence());
  819. if (rendering_mode_ != MediaFoundationRenderingMode::FrameServer) {
  820. return;
  821. }
  822. LONGLONG presentation_timestamp_in_hns = 0;
  823. // OnVideoStreamTick can return S_FALSE if there is no frame available.
  824. if (dxgi_device_manager_ == nullptr ||
  825. mf_media_engine_->OnVideoStreamTick(&presentation_timestamp_in_hns) !=
  826. S_OK) {
  827. return;
  828. }
  829. // TODO(crbug.com/1276067): Change the |native_video_size_| to get the correct
  830. // output video size as determined by the output texture requirements.
  831. gfx::Size video_size = native_video_size_;
  832. base::UnguessableToken frame_token;
  833. auto d3d11_video_frame = texture_pool_.AcquireTexture(&frame_token);
  834. if (d3d11_video_frame.Get() == nullptr)
  835. return;
  836. RECT destination_frame_size = {0, 0, video_size.width(), video_size.height()};
  837. ComPtr<IDXGIKeyedMutex> texture_mutex;
  838. d3d11_video_frame.As(&texture_mutex);
  839. if (texture_mutex->AcquireSync(0, INFINITE) != S_OK) {
  840. texture_pool_.ReleaseTexture(frame_token);
  841. return;
  842. }
  843. if (FAILED(mf_media_engine_->TransferVideoFrame(
  844. d3d11_video_frame.Get(), nullptr, &destination_frame_size,
  845. nullptr))) {
  846. texture_mutex->ReleaseSync(0);
  847. texture_pool_.ReleaseTexture(frame_token);
  848. return;
  849. }
  850. texture_mutex->ReleaseSync(0);
  851. // Need access to GetCurrentTime on the Media Engine.
  852. #undef GetCurrentTime
  853. auto frame_timestamp = base::Seconds(mf_media_engine_->GetCurrentTime());
  854. // Restore previous definition
  855. #define GetCurrentTime() GetTickCount()
  856. frame_available_cb_.Run(frame_token, video_size, frame_timestamp);
  857. }
  858. void MediaFoundationRenderer::NotifyFrameReleased(
  859. const base::UnguessableToken& frame_token) {
  860. texture_pool_.ReleaseTexture(frame_token);
  861. }
  862. } // namespace media