blob: 3bf9746ebfa77429396bacc33d9d34809be72b4f [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "media/base/media_log.h"
#include "media/base/win/mf_helpers.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
namespace media {
MediaFoundationRendererClient::MediaFoundationRendererClient(
scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
std::unique_ptr<MediaLog> media_log,
std::unique_ptr<MojoRenderer> mojo_renderer,
mojo::PendingRemote<RendererExtension> pending_renderer_extension,
std::unique_ptr<DCOMPTextureWrapper> dcomp_texture_wrapper,
VideoRendererSink* sink)
: 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)),
sink_(sink) {
DVLOG_FUNC(1);
}
MediaFoundationRendererClient::~MediaFoundationRendererClient() {
DVLOG_FUNC(1);
}
// Renderer implementation.
void MediaFoundationRendererClient::Initialize(MediaResource* media_resource,
RendererClient* client,
PipelineStatusCallback init_cb) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(!init_cb_);
// Consume and bind the delayed PendingRemote now that we
// are on |media_task_runner_|.
renderer_extension_.Bind(std::move(pending_renderer_extension_),
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();
for (const DemuxerStream* stream : media_streams) {
if (stream->type() == DemuxerStream::Type::VIDEO) {
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(
absl::optional<base::TimeDelta> /*latency_hint*/) {
NOTIMPLEMENTED() << "Latency hint not supported in MediaFoundationRenderer";
}
void MediaFoundationRendererClient::Flush(base::OnceClosure flush_cb) {
mojo_renderer_->Flush(std::move(flush_cb));
}
void MediaFoundationRendererClient::StartPlayingFrom(base::TimeDelta time) {
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();
}
// RendererClient implementation.
void MediaFoundationRendererClient::OnError(PipelineStatus status) {
DVLOG_FUNC(1) << "status=" << status;
client_->OnError(status);
}
void MediaFoundationRendererClient::OnEnded() {
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_->BelongsToCurrentThread());
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(
absl::optional<int> fps) {
DVLOG_FUNC(1) << "fps=" << (fps ? *fps : -1);
DCHECK(has_video_);
client_->OnVideoFrameRateChange(fps);
}
// private
void MediaFoundationRendererClient::OnRemoteRendererInitialized(
PipelineStatus status) {
DVLOG_FUNC(1) << "status=" << status;
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(!init_cb_.is_null());
if (status != PipelineStatus::PIPELINE_OK) {
std::move(init_cb_).Run(status);
return;
}
if (!has_video_) {
std::move(init_cb_).Run(PipelineStatus::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) {
std::move(init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_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_->BelongsToCurrentThread());
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_->BelongsToCurrentThread());
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;
// 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(
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&MediaFoundationRendererClient::OnDCOMPSurfaceReceived,
weak_factory_.GetWeakPtr()),
absl::nullopt));
}
void MediaFoundationRendererClient::OnDCOMPSurfaceReceived(
const absl::optional<base::UnguessableToken>& token) {
DVLOG_FUNC(1);
DCHECK(has_video_);
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (!token) {
MEDIA_LOG(ERROR, media_log_)
<< "Failed to initialize DCOMP mode or failed to get or "
"register DCOMP surface handle on remote renderer";
OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
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_->BelongsToCurrentThread());
DCHECK(has_video_);
if (!success) {
MEDIA_LOG(ERROR, media_log_) << "Failed to set DCOMP surface handle";
OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
}
}
void MediaFoundationRendererClient::OnVideoFrameCreated(
scoped_refptr<VideoFrame> video_frame) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(has_video_);
video_frame->metadata().protected_video = true;
video_frame->metadata().allow_overlay = true;
dcomp_video_frame_ = video_frame;
sink_->PaintSingleFrame(dcomp_video_frame_, true);
}
void MediaFoundationRendererClient::OnCdmAttached(bool success) {
DCHECK(cdm_attached_cb_);
std::move(cdm_attached_cb_).Run(success);
}
void MediaFoundationRendererClient::OnConnectionError() {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->BelongsToCurrentThread());
MEDIA_LOG(ERROR, media_log_) << "MediaFoundationRendererClient disconnected";
OnError(PIPELINE_ERROR_DECODE);
}
} // namespace media