// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/mojo/clients/win/media_foundation_renderer_client.h"
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "media/base/media_log.h"
#include "media/base/win/mf_feature_checks.h"
#include "media/base/win/mf_helpers.h"
#include "media/mojo/mojom/speech_recognition_service.mojom.h"
#include "media/renderers/win/media_foundation_renderer.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace media {
#define REPORT_ERROR_REASON(reason) \
MediaFoundationRenderer::ReportErrorReason( \
MediaFoundationRenderer::ErrorReason::reason)
MediaFoundationRendererClient::MediaFoundationRendererClient(
scoped_refptr<base::SequencedTaskRunner> media_task_runner,
std::unique_ptr<MediaLog> media_log,
std::unique_ptr<MojoRenderer> mojo_renderer,
mojo::PendingRemote<RendererExtension> pending_renderer_extension,
mojo::PendingReceiver<ClientExtension> client_extension_receiver,
std::unique_ptr<DCOMPTextureWrapper> dcomp_texture_wrapper,
ObserveOverlayStateCB observe_overlay_state_cb,
VideoRendererSink* sink,
mojo::PendingRemote<media::mojom::MediaFoundationRendererObserver>
media_foundation_renderer_observer)
: media_task_runner_(std::move(media_task_runner)),
media_log_(std::move(media_log)),
mojo_renderer_(std::move(mojo_renderer)),
pending_renderer_extension_(std::move(pending_renderer_extension)),
dcomp_texture_wrapper_(std::move(dcomp_texture_wrapper)),
observe_overlay_state_cb_(std::move(observe_overlay_state_cb)),
sink_(sink),
pending_client_extension_receiver_(std::move(client_extension_receiver)),
client_extension_receiver_(this),
pending_media_foundation_renderer_observer_(
std::move(media_foundation_renderer_observer)) {
DVLOG_FUNC(1);
}
MediaFoundationRendererClient::~MediaFoundationRendererClient() {
DVLOG_FUNC(1);
SignalMediaPlayingStateChange(false);
}
// Renderer implementation.
void MediaFoundationRendererClient::Initialize(MediaResource* media_resource,
RendererClient* client,
PipelineStatusCallback init_cb) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(!init_cb_);
if (!dcomp_texture_wrapper_) {
MEDIA_LOG(ERROR, media_log_) << "Failed to create DCOMPTextureWrapper";
REPORT_ERROR_REASON(kFailedToCreateDCompTextureWrapper);
std::move(init_cb).Run({PIPELINE_ERROR_INITIALIZATION_FAILED,
"DComTextureWrapper creation failed"});
return;
}
// Consume and bind the delayed PendingRemote and PendingReceiver now that
// we are on |media_task_runner_|.
renderer_extension_.Bind(std::move(pending_renderer_extension_),
media_task_runner_);
media_foundation_renderer_observer_.Bind(
std::move(pending_media_foundation_renderer_observer_),
media_task_runner_);
client_extension_receiver_.Bind(std::move(pending_client_extension_receiver_),
media_task_runner_);
// Handle unexpected mojo pipe disconnection such as "mf_cdm" utility process
// crashed or killed in Browser task manager.
renderer_extension_.set_disconnect_handler(
base::BindOnce(&MediaFoundationRendererClient::OnConnectionError,
base::Unretained(this)));
client_ = client;
init_cb_ = std::move(init_cb);
auto media_streams = media_resource->GetAllStreams();
// Check the rendering strategy & whether we're operating on clear or
// protected content to determine the starting 'rendering_mode_'.
// If the Direct Composition strategy is specified or if we're operating on
// protected content then start in Direct Composition mode, else start in
// Frame Server mode. This behavior must match the logic in
// MediaFoundationRenderer::Initialize.
rendering_strategy_ = kMediaFoundationClearRenderingStrategyParam.Get();
LogRenderingStrategy();
rendering_mode_ =
rendering_strategy_ ==
MediaFoundationClearRenderingStrategy::kDirectComposition
? MediaFoundationRenderingMode::DirectComposition
: MediaFoundationRenderingMode::FrameServer;
// Start off at 60 fps for our render interval, however it will be updated
// later in OnVideoFrameRateChange
render_interval_ = base::Microseconds(16666);
for (DemuxerStream* stream : media_streams) {
if (stream->type() == DemuxerStream::Type::VIDEO) {
if (stream->video_decoder_config().is_encrypted()) {
// This is protected content which only supports Direct Composition
// mode, update 'rendering_mode_' accordingly.
rendering_mode_ = MediaFoundationRenderingMode::DirectComposition;
}
has_video_ = true;
break;
}
}
mojo_renderer_->Initialize(
media_resource, this,
base::BindOnce(
&MediaFoundationRendererClient::OnRemoteRendererInitialized,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::SetCdm(CdmContext* cdm_context,
CdmAttachedCB cdm_attached_cb) {
DVLOG_FUNC(1) << "cdm_context=" << cdm_context;
DCHECK(cdm_context);
if (cdm_context_) {
DLOG(ERROR) << "Switching CDM not supported";
std::move(cdm_attached_cb).Run(false);
return;
}
cdm_context_ = cdm_context;
DCHECK(cdm_attached_cb_.is_null());
cdm_attached_cb_ = std::move(cdm_attached_cb);
mojo_renderer_->SetCdm(
cdm_context_,
base::BindOnce(&MediaFoundationRendererClient::OnCdmAttached,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::SetLatencyHint(
std::optional<base::TimeDelta> latency_hint) {
mojo_renderer_->SetLatencyHint(latency_hint);
}
void MediaFoundationRendererClient::Flush(base::OnceClosure flush_cb) {
mojo_renderer_->Flush(std::move(flush_cb));
}
void MediaFoundationRendererClient::StartPlayingFrom(base::TimeDelta time) {
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
SignalMediaPlayingStateChange(true);
next_video_frame_.reset();
mojo_renderer_->StartPlayingFrom(time);
}
void MediaFoundationRendererClient::SetPlaybackRate(double playback_rate) {
mojo_renderer_->SetPlaybackRate(playback_rate);
}
void MediaFoundationRendererClient::SetVolume(float volume) {
mojo_renderer_->SetVolume(volume);
}
base::TimeDelta MediaFoundationRendererClient::GetMediaTime() {
return mojo_renderer_->GetMediaTime();
}
void MediaFoundationRendererClient::OnSelectedVideoTracksChanged(
const std::vector<DemuxerStream*>& enabled_tracks,
base::OnceClosure change_completed_cb) {
bool video_track_selected = (enabled_tracks.size() > 0);
DVLOG_FUNC(1) << "video_track_selected=" << video_track_selected;
renderer_extension_->SetVideoStreamEnabled(video_track_selected);
std::move(change_completed_cb).Run();
}
void MediaFoundationRendererClient::OnExternalVideoFrameRequest() {
// A frame read back signal is currently treated as a permanent signal for
// the session so we only need to handle it the first time it is encountered.
if (!has_frame_read_back_signal_) {
has_frame_read_back_signal_ = true;
MEDIA_LOG(INFO, media_log_) << "Frame read back signal";
UpdateRenderMode();
}
}
RendererType MediaFoundationRendererClient::GetRendererType() {
return RendererType::kMediaFoundation;
}
// RendererClient implementation.
void MediaFoundationRendererClient::OnError(PipelineStatus status) {
DVLOG_FUNC(1) << "status=" << status;
SignalMediaPlayingStateChange(false);
// When hardware context reset happens, presenting the `dcomp_video_frame_`
// could cause issues like black screen flash (see crbug.com/1384544).
// Render a black frame to avoid this issue. This is fine since the player
// is already in an error state and `this` will be recreated.
if (status == PIPELINE_ERROR_HARDWARE_CONTEXT_RESET && dcomp_video_frame_ &&
!IsFrameServerMode()) {
dcomp_video_frame_.reset();
dcomp_frame_observer_subscription_.reset();
auto black_frame = media::VideoFrame::CreateBlackFrame(natural_size_);
sink_->PaintSingleFrame(black_frame, true);
}
// Do not call MediaFoundationRenderer::ReportErrorReason() since it should've
// already been reported in MediaFoundationRenderer.
client_->OnError(status);
}
void MediaFoundationRendererClient::OnFallback(PipelineStatus fallback) {
SignalMediaPlayingStateChange(false);
client_->OnFallback(std::move(fallback).AddHere());
}
void MediaFoundationRendererClient::OnEnded() {
SignalMediaPlayingStateChange(false);
client_->OnEnded();
}
void MediaFoundationRendererClient::OnStatisticsUpdate(
const PipelineStatistics& stats) {
client_->OnStatisticsUpdate(stats);
}
void MediaFoundationRendererClient::OnBufferingStateChange(
BufferingState state,
BufferingStateChangeReason reason) {
client_->OnBufferingStateChange(state, reason);
}
void MediaFoundationRendererClient::OnWaiting(WaitingReason reason) {
client_->OnWaiting(reason);
}
void MediaFoundationRendererClient::OnAudioConfigChange(
const AudioDecoderConfig& config) {
client_->OnAudioConfigChange(config);
}
void MediaFoundationRendererClient::OnVideoConfigChange(
const VideoDecoderConfig& config) {
client_->OnVideoConfigChange(config);
}
void MediaFoundationRendererClient::OnVideoNaturalSizeChange(
const gfx::Size& size) {
DVLOG_FUNC(1) << "size=" << size.ToString();
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
natural_size_ = size;
dcomp_texture_wrapper_->CreateVideoFrame(
natural_size_,
base::BindOnce(&MediaFoundationRendererClient::OnVideoFrameCreated,
weak_factory_.GetWeakPtr()));
client_->OnVideoNaturalSizeChange(natural_size_);
}
void MediaFoundationRendererClient::OnVideoOpacityChange(bool opaque) {
DVLOG_FUNC(1) << "opaque=" << opaque;
DCHECK(has_video_);
client_->OnVideoOpacityChange(opaque);
}
void MediaFoundationRendererClient::OnVideoFrameRateChange(
std::optional<int> fps) {
DVLOG_FUNC(1) << "fps=" << (fps ? *fps : -1);
DCHECK(has_video_);
if (fps.has_value()) {
// We use microseconds as that is the max resolution of TimeDelta
render_interval_ = base::Microseconds(1000000 / *fps);
}
client_->OnVideoFrameRateChange(fps);
}
// RenderCallback implementation.
scoped_refptr<VideoFrame> MediaFoundationRendererClient::Render(
base::TimeTicks deadline_min,
base::TimeTicks deadline_max,
RenderingMode mode) {
// Sends a frame request if in frame server mode, otherwise return nothing as
// it is rendered independently by Windows Direct Composition.
if (!IsFrameServerMode()) {
return nullptr;
}
auto callback =
[](base::WeakPtr<MediaFoundationRendererClient> renderer_client) {
if (renderer_client) {
renderer_client->renderer_extension_->RequestNextFrame();
}
};
media_task_runner_->PostTask(
FROM_HERE, base::BindOnce(callback, weak_factory_.GetWeakPtr()));
// TODO(crbug.com/40822735): Need to report underflow when we don't have a
// frame ready for presentation by calling OnBufferingStateChange
return next_video_frame_;
}
void MediaFoundationRendererClient::OnFrameDropped() {
// TODO(crbug.com/40822735): Need to notify when frames were not presented.
return;
}
base::TimeDelta MediaFoundationRendererClient::GetPreferredRenderInterval() {
return render_interval_;
}
// media::mojom::MediaFoundationRendererClientExtension
void MediaFoundationRendererClient::InitializeFramePool(
mojom::FramePoolInitializationParametersPtr pool_info) {
DCHECK_GT(pool_info->frame_textures.size(), static_cast<size_t>(0));
// Release our references to the video pool so that once the
// rendering is complete the memory will be freed.
video_frame_pool_.clear();
for (const auto& frame_info : pool_info->frame_textures) {
dcomp_texture_wrapper_->CreateVideoFrame(
pool_info->texture_size, std::move(frame_info->texture_handle),
base::BindOnce(
&MediaFoundationRendererClient::OnFramePoolVideoFrameCreated,
weak_factory_.GetWeakPtr(), frame_info->token));
}
}
void MediaFoundationRendererClient::OnFrameAvailable(
const base::UnguessableToken& frame_token,
const gfx::Size& size,
base::TimeDelta timestamp) {
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
auto frame_pool_entry = video_frame_pool_.find(frame_token);
// It is possible to become unsynced when we are reinitializing the frame
// pool so we are just checking to make sure the frame has been acquired.
if (frame_pool_entry == video_frame_pool_.end()) {
return;
}
auto* video_frame_pair = &(frame_pool_entry->second);
scoped_refptr<VideoFrame> texture_pool_video_frame = video_frame_pair->first;
texture_pool_video_frame->set_timestamp(timestamp);
// The Video Frame object's Destruction Observer is called when the video
// frame is no longer needed and the underlying texture can be reused. We
// cannot use the video frame we created in InitializeFramePool() directly
// because we hold onto a reference in our video frame pool so the callback
// would not be called, and for those their callback is to destroy the shared
// image anyway. Therefore we wrap the shared image based video frame in
// another video frame and add the callback which allows us to reuse the
// texture for a new video frame.
scoped_refptr<VideoFrame> frame = VideoFrame::WrapVideoFrame(
texture_pool_video_frame, texture_pool_video_frame->format(),
gfx::Rect(size), size);
if (!frame) {
MEDIA_LOG(WARNING, media_log_)
<< "OnFrameAvailable failed to wrap a VideoFrame";
return;
}
frame->metadata().wants_promotion_hint = true;
frame->metadata().allow_overlay = true;
frame->AddDestructionObserver(base::BindPostTask(
media_task_runner_,
base::BindOnce(&MediaFoundationRendererClient::OnPaintComplete,
weak_factory_.GetWeakPtr(), frame_token)));
// The sink needs a frame ASAP so the first frame will be painted, all
// following frames will be returned in the Render callback.
if (!next_video_frame_) {
sink_->PaintSingleFrame(frame);
}
next_video_frame_ = frame;
}
// private
bool MediaFoundationRendererClient::IsFrameServerMode() const {
return rendering_mode_ == MediaFoundationRenderingMode::FrameServer;
}
void MediaFoundationRendererClient::OnConnectionError() {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
MEDIA_LOG(ERROR, media_log_) << "MediaFoundationRendererClient disconnected";
REPORT_ERROR_REASON(kOnConnectionError);
OnError(PIPELINE_ERROR_DISCONNECTED);
}
void MediaFoundationRendererClient::OnRemoteRendererInitialized(
PipelineStatus status) {
DVLOG_FUNC(1) << "status=" << status;
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(!init_cb_.is_null());
if (status != PIPELINE_OK) {
std::move(init_cb_).Run(status);
return;
}
if (!has_video_) {
std::move(init_cb_).Run(PIPELINE_OK);
return;
}
// For playback with video, initialize `dcomp_texture_wrapper_` for direct
// composition.
bool success = dcomp_texture_wrapper_->Initialize(
gfx::Size(1, 1),
base::BindRepeating(&MediaFoundationRendererClient::OnOutputRectChange,
weak_factory_.GetWeakPtr()));
if (!success) {
REPORT_ERROR_REASON(kFailedToInitDCompTextureWrapper);
std::move(init_cb_).Run({PIPELINE_ERROR_INITIALIZATION_FAILED,
"DComTextureWrapper init failed"});
return;
}
// Initialize DCOMP texture size to {1, 1} to signify to SwapChainPresenter
// that the video output size is not yet known.
if (output_size_.IsEmpty())
dcomp_texture_wrapper_->UpdateTextureSize(gfx::Size(1, 1));
std::move(init_cb_).Run(PIPELINE_OK);
}
void MediaFoundationRendererClient::OnOutputRectChange(gfx::Rect output_rect) {
DVLOG_FUNC(1) << "output_rect=" << output_rect.ToString();
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
renderer_extension_->SetOutputRect(
output_rect,
base::BindOnce(&MediaFoundationRendererClient::OnSetOutputRectDone,
weak_factory_.GetWeakPtr(), output_rect.size()));
}
void MediaFoundationRendererClient::OnSetOutputRectDone(
const gfx::Size& output_size,
bool success) {
DVLOG_FUNC(1) << "output_size=" << output_size.ToString();
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
if (!success) {
DLOG(ERROR) << "Failed to SetOutputRect";
MEDIA_LOG(WARNING, media_log_) << "Failed to SetOutputRect";
// Ignore this error as video can possibly be seen but displayed incorrectly
// against the video output area.
return;
}
output_size_ = output_size;
if (output_size_updated_)
return;
if (IsFrameServerMode()) {
return;
}
// Call UpdateTextureSize() only 1 time to indicate DCOMP rendering is
// ready. The actual size does not matter as long as it is not empty and not
// (1x1).
if (!output_size_.IsEmpty() && output_size_ != gfx::Size(1, 1)) {
dcomp_texture_wrapper_->UpdateTextureSize(output_size_);
output_size_updated_ = true;
}
InitializeDCOMPRenderingIfNeeded();
// Ensures `SwapChainPresenter::PresentDCOMPSurface()` is invoked to add
// video into DCOMP visual tree if needed.
if (dcomp_video_frame_) {
sink_->PaintSingleFrame(dcomp_video_frame_, true);
}
}
void MediaFoundationRendererClient::InitializeDCOMPRenderingIfNeeded() {
DVLOG_FUNC(1);
DCHECK(has_video_);
if (dcomp_rendering_initialized_)
return;
dcomp_rendering_initialized_ = true;
// Set DirectComposition mode and get DirectComposition surface from
// MediaFoundationRenderer.
renderer_extension_->GetDCOMPSurface(
base::BindOnce(&MediaFoundationRendererClient::OnDCOMPSurfaceReceived,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::OnDCOMPSurfaceReceived(
const std::optional<base::UnguessableToken>& token,
const std::string& error) {
DVLOG_FUNC(1);
DCHECK(has_video_);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
// The error should've already been handled in MediaFoundationRenderer.
if (!token) {
DLOG(ERROR) << "GetDCOMPSurface failed: " + error;
return;
}
dcomp_texture_wrapper_->SetDCOMPSurfaceHandle(
token.value(),
base::BindOnce(&MediaFoundationRendererClient::OnDCOMPSurfaceHandleSet,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::OnDCOMPSurfaceHandleSet(bool success) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
if (!success) {
MEDIA_LOG(ERROR, media_log_) << "Failed to set DCOMP surface handle";
REPORT_ERROR_REASON(kOnDCompSurfaceHandleSetError);
OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
return;
}
// Ensure `SwapChainPresenter::PresentDCOMPSurface()` is invoked to add video
// into DCOMP visual tree since `DCOMPTexture::SetDCOMPSurfaceHandle()`
// has just succeeded.
if (dcomp_video_frame_ && !IsFrameServerMode()) {
sink_->PaintSingleFrame(dcomp_video_frame_,
/*repaint_duplicate_frame=*/true);
}
}
void MediaFoundationRendererClient::OnVideoFrameCreated(
scoped_refptr<VideoFrame> video_frame,
const gpu::Mailbox& mailbox) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
video_frame->metadata().allow_overlay = true;
if (cdm_context_) {
video_frame->metadata().protected_video = true;
video_frame->metadata().hw_protected = true;
dcomp_frame_observer_subscription_.reset();
} else {
DCHECK(SupportMediaFoundationClearPlayback());
// This video frame is for clear content: setup observation of the mailbox
// overlay state changes.
video_frame->metadata().wants_promotion_hint = true;
dcomp_frame_observer_subscription_ = ObserveMailboxForOverlayState(mailbox);
}
dcomp_video_frame_ = std::move(video_frame);
if (!IsFrameServerMode()) {
sink_->PaintSingleFrame(dcomp_video_frame_, true);
}
}
void MediaFoundationRendererClient::OnFramePoolVideoFrameCreated(
const base::UnguessableToken& token,
scoped_refptr<VideoFrame> video_frame,
const gpu::Mailbox& mailbox) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
std::unique_ptr<OverlayStateObserverSubscription> observer_subscription =
ObserveMailboxForOverlayState(mailbox);
video_frame_pool_.insert(
{token, std::make_pair(std::move(video_frame),
std::move(observer_subscription))});
}
void MediaFoundationRendererClient::OnCdmAttached(bool success) {
DCHECK(cdm_attached_cb_);
std::move(cdm_attached_cb_).Run(success);
}
void MediaFoundationRendererClient::SignalMediaPlayingStateChange(
bool is_playing) {
// Skip if we are already in the same playing state
if (is_playing == is_playing_) {
return;
}
// Only start the render loop if we are in frame server mode
if (IsFrameServerMode()) {
if (is_playing) {
sink_->Start(this);
} else {
sink_->Stop();
}
}
is_playing_ = is_playing;
}
std::unique_ptr<OverlayStateObserverSubscription>
MediaFoundationRendererClient::ObserveMailboxForOverlayState(
const gpu::Mailbox& mailbox) {
std::unique_ptr<OverlayStateObserverSubscription> observer_subscription;
// If the rendering strategy is dynamic then setup an OverlayStateObserver to
// respond to promotion changes. If the rendering strategy is Direct
// Composition or Frame Server then we do not need to listen & respond to
// overlay state changes.
if (rendering_strategy_ == MediaFoundationClearRenderingStrategy::kDynamic) {
// 'observe_overlay_state_cb_' creates a content::OverlayStateObserver to
// subscribe to overlay state information for the given 'mailbox' from the
// Viz layer in the GPU process. We hold an OverlayStateObserverSubscription
// since a direct dependency on a content object is not allowed. Once the
// OverlayStateObserverSubscription is destroyed the OnOverlayStateChanged
// callback will no longer be invoked, so base::Unretained(this) is safe to
// use.
observer_subscription = observe_overlay_state_cb_.Run(
mailbox, base::BindRepeating(
&MediaFoundationRendererClient::OnOverlayStateChanged,
base::Unretained(this), mailbox));
DCHECK(observer_subscription);
}
return observer_subscription;
}
void MediaFoundationRendererClient::OnOverlayStateChanged(
const gpu::Mailbox& mailbox,
bool promoted) {
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
promoted_to_overlay_signal_ = promoted;
MEDIA_LOG(INFO, media_log_)
<< "Overlay state signal, promoted = " << promoted;
UpdateRenderMode();
}
void MediaFoundationRendererClient::UpdateRenderMode() {
// We only change modes if we're using the dynamic rendering strategy and
// presenting clear content, so return early otherwise.
if (rendering_strategy_ != MediaFoundationClearRenderingStrategy::kDynamic ||
cdm_context_) {
return;
}
// Frame Server mode is required if we are not promoted to an overlay or if
// frame readback is required.
bool needs_frame_server =
has_frame_read_back_signal_ || !promoted_to_overlay_signal_;
if (!needs_frame_server && IsFrameServerMode()) {
MEDIA_LOG(INFO, media_log_) << "Switching to Direct Composition.";
// Switch to Frame Server Mode
// Switch to Direct Composition mode.
rendering_mode_ = MediaFoundationRenderingMode::DirectComposition;
renderer_extension_->SetMediaFoundationRenderingMode(rendering_mode_);
if (is_playing_) {
sink_->Stop();
}
// If we don't have a DComp Visual then create one, otherwise paint
// DComp frame again.
if (!dcomp_video_frame_) {
InitializeDCOMPRenderingIfNeeded();
} else {
sink_->PaintSingleFrame(dcomp_video_frame_, true);
}
} else if (needs_frame_server && !IsFrameServerMode()) {
// Switch to Frame Server mode.
MEDIA_LOG(INFO, media_log_) << "Switching to Frame Server.";
rendering_mode_ = MediaFoundationRenderingMode::FrameServer;
renderer_extension_->SetMediaFoundationRenderingMode(rendering_mode_);
if (is_playing_) {
sink_->Start(this);
}
}
}
void MediaFoundationRendererClient::OnPaintComplete(
const base::UnguessableToken& token) {
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
renderer_extension_->NotifyFrameReleased(token);
}
void MediaFoundationRendererClient::LogRenderingStrategy() {
std::string strategy;
switch (rendering_strategy_) {
case MediaFoundationClearRenderingStrategy::kDirectComposition:
strategy = "Direct Composition";
break;
case MediaFoundationClearRenderingStrategy::kFrameServer:
strategy = "Frame Server";
break;
case MediaFoundationClearRenderingStrategy::kDynamic:
strategy = "Dynamic";
break;
default:
strategy = "Unknown";
break;
}
MEDIA_LOG(INFO, media_log_)
<< "MediaFoundationClearRenderingStrategy: " << strategy;
}
} // namespace media