blob: c794b29ec73696eabdd6cafeff82ef24b191de9b [file] [log] [blame]
// Copyright 2018 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 "content/browser/media/capture/frame_sink_video_capture_device.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "content/browser/compositor/surface_utils.h"
#include "media/base/bind_to_current_loop.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
#include "mojo/public/cpp/system/buffer.h"
namespace content {
namespace {
// Transfers ownership of an object to a std::unique_ptr with a custom deleter
// that ensures the object is destroyed on the UI BrowserThread.
template <typename T>
std::unique_ptr<T, BrowserThread::DeleteOnUIThread> RescopeToUIThread(
std::unique_ptr<T>&& ptr) {
return std::unique_ptr<T, BrowserThread::DeleteOnUIThread>(ptr.release());
}
// Sets up a mojo message pipe and requests the HostFrameSinkManager create a
// new capturer instance bound to it. Returns the client-side interface.
viz::mojom::FrameSinkVideoCapturerPtrInfo CreateCapturer() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
viz::HostFrameSinkManager* const manager = GetHostFrameSinkManager();
DCHECK(manager);
viz::mojom::FrameSinkVideoCapturerPtr capturer;
manager->CreateVideoCapturer(mojo::MakeRequest(&capturer));
return capturer.PassInterface();
}
// Adapter for a VideoFrameReceiver to get access to the mojo SharedBufferHandle
// for a frame.
class HandleMover
: public media::VideoCaptureDevice::Client::Buffer::HandleProvider {
public:
explicit HandleMover(mojo::ScopedSharedBufferHandle handle)
: handle_(std::move(handle)) {}
~HandleMover() final {}
mojo::ScopedSharedBufferHandle GetHandleForInterProcessTransit(
bool read_only) final {
return std::move(handle_);
}
base::SharedMemoryHandle GetNonOwnedSharedMemoryHandleForLegacyIPC() final {
NOTREACHED();
return base::SharedMemoryHandle();
}
std::unique_ptr<media::VideoCaptureBufferHandle> GetHandleForInProcessAccess()
final {
NOTREACHED();
return nullptr;
}
private:
mojo::ScopedSharedBufferHandle handle_;
};
// Adapter for a VideoFrameReceiver to notify once frame consumption is
// complete. VideoFrameReceiver requires owning an object that it will destroy
// once consumption is complete. This class adapts between that scheme and
// running a "done callback" to notify that consumption is complete.
class ScopedFrameDoneHelper
: public base::ScopedClosureRunner,
public media::VideoCaptureDevice::Client::Buffer::ScopedAccessPermission {
public:
ScopedFrameDoneHelper(base::OnceClosure done_callback)
: base::ScopedClosureRunner(std::move(done_callback)) {}
~ScopedFrameDoneHelper() final = default;
};
} // namespace
FrameSinkVideoCaptureDevice::FrameSinkVideoCaptureDevice()
: capturer_creator_(base::BindRepeating(&CreateCapturer)),
binding_(this),
cursor_renderer_(RescopeToUIThread(CursorRenderer::Create(
CursorRenderer::CURSOR_DISPLAYED_ON_MOUSE_MOVEMENT))),
weak_factory_(this) {
DCHECK(cursor_renderer_);
}
FrameSinkVideoCaptureDevice::~FrameSinkVideoCaptureDevice() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!receiver_) << "StopAndDeAllocate() was never called after start.";
}
void FrameSinkVideoCaptureDevice::AllocateAndStartWithReceiver(
const media::VideoCaptureParams& params,
std::unique_ptr<media::VideoFrameReceiver> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(params.IsValid());
DCHECK(receiver);
// If the device has already ended on a fatal error, abort immediately.
if (fatal_error_message_) {
receiver->OnLog(*fatal_error_message_);
receiver->OnError();
return;
}
capture_params_ = params;
WillStart();
DCHECK(!receiver_);
receiver_ = std::move(receiver);
// Set a callback that will be run whenever the mouse moves, to trampoline
// back to the device thread and request a refresh frame so that the new mouse
// cursor location can be drawn in a new video frame.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&CursorRenderer::SetNeedsRedrawCallback,
cursor_renderer_->GetWeakPtr(),
media::BindToCurrentLoop(base::BindRepeating(
&FrameSinkVideoCaptureDevice::RequestRefreshFrame,
weak_factory_.GetWeakPtr()))));
// Hop to the UI thread to request a Mojo connection to a new capturer
// instance, and then hop back to the device thread to start using it.
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::UI, FROM_HERE, base::BindOnce(capturer_creator_),
base::BindOnce(&FrameSinkVideoCaptureDevice::OnCapturerCreated,
weak_factory_.GetWeakPtr()));
}
void FrameSinkVideoCaptureDevice::AllocateAndStart(
const media::VideoCaptureParams& params,
std::unique_ptr<media::VideoCaptureDevice::Client> client) {
// FrameSinkVideoCaptureDevice does not use a
// VideoCaptureDevice::Client. Instead, it provides frames to a
// VideoFrameReceiver directly.
NOTREACHED();
}
void FrameSinkVideoCaptureDevice::RequestRefreshFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (capturer_ && binding_.is_bound() && !suspend_requested_) {
capturer_->RequestRefreshFrame();
}
}
void FrameSinkVideoCaptureDevice::MaybeSuspend() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
suspend_requested_ = true;
MaybeStopConsuming();
}
void FrameSinkVideoCaptureDevice::Resume() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
suspend_requested_ = false;
MaybeStartConsuming();
}
void FrameSinkVideoCaptureDevice::StopAndDeAllocate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Discontinue requesting extra video frames to render mouse cursor changes.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&CursorRenderer::SetNeedsRedrawCallback,
cursor_renderer_->GetWeakPtr(), base::RepeatingClosure()));
MaybeStopConsuming();
capturer_.reset();
if (receiver_) {
receiver_.reset();
DidStop();
}
}
void FrameSinkVideoCaptureDevice::OnUtilizationReport(int frame_feedback_id,
double utilization) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Assumption: The "slot" should be valid at this point because this method
// will always be called before the VideoFrameReceiver signals it is done
// consuming the frame.
const auto slot_index = static_cast<size_t>(frame_feedback_id);
DCHECK_LT(slot_index, slots_.size());
slots_[slot_index].callbacks->ProvideFeedback(utilization);
}
void FrameSinkVideoCaptureDevice::OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callbacks);
if (!receiver_ || !buffer.is_valid()) {
callbacks->Done();
return;
}
// Search for the next available ConsumptionState slot and bind |callbacks|
// there.
size_t slot_index = 0;
for (;; ++slot_index) {
if (slot_index == slots_.size()) {
// The growth of |slots_| should be bounded because the
// viz::mojom::FrameSinkVideoCapturer should enforce an upper-bound on the
// number of frames in-flight.
constexpr size_t kMaxInFlightFrames = 32; // Arbitrarily-chosen limit.
DCHECK_LT(slots_.size(), kMaxInFlightFrames);
slots_.emplace_back();
break;
}
if (!slots_[slot_index].callbacks.is_bound()) {
break;
}
}
ConsumptionState& slot = slots_[slot_index];
slot.callbacks = std::move(callbacks);
// Render the mouse cursor on the video frame, but first map the shared memory
// into the current process in order to render the cursor.
mojo::ScopedSharedBufferMapping mapping = buffer->Map(buffer_size);
scoped_refptr<media::VideoFrame> frame;
if (mapping) {
frame = media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, info->visible_rect,
info->visible_rect.size(), static_cast<uint8_t*>(mapping.get()),
buffer_size, info->timestamp);
if (frame) {
frame->AddDestructionObserver(base::BindOnce(
[](mojo::ScopedSharedBufferMapping mapping) {}, std::move(mapping)));
if (!cursor_renderer_->RenderOnVideoFrame(frame.get(), content_rect,
&slot.undoer)) {
// Release |frame| now, since no "undo cursor rendering" will be needed.
frame = nullptr;
}
}
}
// Set the INTERACTIVE_CONTENT frame metadata.
media::VideoFrameMetadata modified_metadata;
if (info->metadata) {
modified_metadata.MergeInternalValuesFrom(*info->metadata);
}
modified_metadata.SetBoolean(media::VideoFrameMetadata::INTERACTIVE_CONTENT,
cursor_renderer_->IsUserInteractingWithView());
info->metadata = modified_metadata.CopyInternalValues();
// Pass the video frame to the VideoFrameReceiver. This is done by first
// passing the shared memory buffer handle and then notifying it that a new
// frame is ready to be read from the buffer.
receiver_->OnNewBufferHandle(
static_cast<BufferId>(slot_index),
std::make_unique<HandleMover>(std::move(buffer)));
receiver_->OnFrameReadyInBuffer(
static_cast<BufferId>(slot_index), slot_index,
std::make_unique<ScopedFrameDoneHelper>(
media::BindToCurrentLoop(base::BindOnce(
&FrameSinkVideoCaptureDevice::OnFramePropagationComplete,
weak_factory_.GetWeakPtr(), slot_index, std::move(frame)))),
std::move(info));
}
void FrameSinkVideoCaptureDevice::OnTargetLost(
const viz::FrameSinkId& frame_sink_id) {
// This is ignored because FrameSinkVideoCaptureDevice subclasses always call
// OnTargetChanged() and OnTargetPermanentlyLost() to resolve lost targets.
}
void FrameSinkVideoCaptureDevice::OnStopped() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This method would never be called if FrameSinkVideoCaptureDevice explicitly
// called capturer_->Stop(), because the binding is closed at that time.
// Therefore, a call to this method means that the capturer cannot continue;
// and that's a permanent failure.
OnFatalError("Capturer service cannot continue.");
}
void FrameSinkVideoCaptureDevice::OnTargetChanged(
const viz::FrameSinkId& frame_sink_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
target_ = frame_sink_id;
// TODO(crbug.com/754872): When the frame sink is invalid, the capturer should
// be told there is no target. This will require a mojo API change; and will
// be addressed in a soon-upcoming CL.
if (capturer_ && frame_sink_id.is_valid()) {
capturer_->ChangeTarget(frame_sink_id);
}
}
void FrameSinkVideoCaptureDevice::OnTargetPermanentlyLost() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
target_ = viz::FrameSinkId();
OnFatalError("Capture target has been permanently lost.");
}
void FrameSinkVideoCaptureDevice::SetCapturerCreatorForTesting(
CapturerCreatorCallback creator) {
capturer_creator_ = std::move(creator);
}
void FrameSinkVideoCaptureDevice::WillStart() {}
void FrameSinkVideoCaptureDevice::DidStop() {}
void FrameSinkVideoCaptureDevice::OnCapturerCreated(
viz::mojom::FrameSinkVideoCapturerPtrInfo info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!receiver_) {
return; // StopAndDeAllocate() occurred in the meantime.
}
// Shutdown the prior capturer, if any.
MaybeStopConsuming();
capturer_.reset();
// Bind and configure the new capturer.
capturer_.Bind(std::move(info));
// TODO(miu): Remove this once HostFrameSinkManager will notify this
// consumer when to take recovery steps after VIZ process crashes.
capturer_.set_connection_error_handler(base::BindOnce(
&FrameSinkVideoCaptureDevice::OnFatalError, base::Unretained(this),
"Capturer service connection lost."));
capturer_->SetFormat(capture_params_.requested_format.pixel_format,
media::COLOR_SPACE_UNSPECIFIED);
capturer_->SetMinCapturePeriod(
base::TimeDelta::FromMicroseconds(base::saturated_cast<int64_t>(
base::Time::kMicrosecondsPerSecond /
capture_params_.requested_format.frame_rate)));
const auto& constraints = capture_params_.SuggestConstraints();
capturer_->SetResolutionConstraints(constraints.min_frame_size,
constraints.max_frame_size,
constraints.fixed_aspect_ratio);
if (target_.is_valid()) {
capturer_->ChangeTarget(target_);
}
receiver_->OnStarted();
if (!suspend_requested_) {
MaybeStartConsuming();
}
}
void FrameSinkVideoCaptureDevice::MaybeStartConsuming() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!receiver_ || !capturer_ || binding_.is_bound()) {
return;
}
viz::mojom::FrameSinkVideoConsumerPtr consumer;
binding_.Bind(mojo::MakeRequest(&consumer));
// TODO(miu): Remove this once HostFrameSinkManager will notify this consumer
// when to take recovery steps after VIZ process crashes.
binding_.set_connection_error_handler(base::BindOnce(
&FrameSinkVideoCaptureDevice::OnFatalError, base::Unretained(this),
"Consumer connection to Capturer service lost."));
capturer_->Start(std::move(consumer));
}
void FrameSinkVideoCaptureDevice::MaybeStopConsuming() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (capturer_) {
capturer_->Stop();
}
binding_.Close();
}
void FrameSinkVideoCaptureDevice::OnFramePropagationComplete(
size_t slot_index,
scoped_refptr<media::VideoFrame> frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_LT(slot_index, slots_.size());
// Notify the VideoFrameReceiver that the buffer is no longer valid.
if (receiver_) {
receiver_->OnBufferRetired(static_cast<BufferId>(slot_index));
}
// Undo the mouse cursor rendering, if any.
ConsumptionState& slot = slots_[slot_index];
if (frame) {
slot.undoer.Undo(frame.get());
frame = nullptr;
}
// Notify the capturer that consumption of the frame is complete.
slot.callbacks->Done();
slot.callbacks.reset();
}
void FrameSinkVideoCaptureDevice::OnFatalError(std::string message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
fatal_error_message_ = std::move(message);
if (receiver_) {
receiver_->OnLog(*fatal_error_message_);
receiver_->OnError();
}
StopAndDeAllocate();
}
FrameSinkVideoCaptureDevice::ConsumptionState::ConsumptionState() = default;
FrameSinkVideoCaptureDevice::ConsumptionState::~ConsumptionState() = default;
FrameSinkVideoCaptureDevice::ConsumptionState::ConsumptionState(
FrameSinkVideoCaptureDevice::ConsumptionState&& other) = default;
FrameSinkVideoCaptureDevice::ConsumptionState&
FrameSinkVideoCaptureDevice::ConsumptionState::operator=(
FrameSinkVideoCaptureDevice::ConsumptionState&& other) = default;
} // namespace content