Commit 209b438b authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

Integrate VideoCaptureOverlays into viz::FrameSinkVideoCapturer.

Introduces the mojo IDL and implementation "glue" to be able to create
overlays to be rendered on top of each video frame captured by a
FrameSinkVideoCapturer. This allows for use cases like mouse cursor
rendering and debug status displays. Also, the new overlay APIs have
been added to ClientFrameSinkVideoCapturer (a client-side wrapper that
handles the auto-reconnecting-to-VIZ after a VIZ process crash).

Bug: 810133
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel
Change-Id: I963b6a1d736e1510618a723694f3b22ac0bf1589
Reviewed-on: https://chromium-review.googlesource.com/1150929
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Reviewed-by: default avatarSaman Sami <samans@chromium.org>
Reviewed-by: default avatarMustafa Emre Acer <meacer@chromium.org>
Reviewed-by: default avatarXiangjun Zhang <xjz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#579654}
parent 50039cd3
......@@ -24,22 +24,30 @@ ClientFrameSinkVideoCapturer::ClientFrameSinkVideoCapturer(
EstablishConnection();
}
ClientFrameSinkVideoCapturer::~ClientFrameSinkVideoCapturer() = default;
ClientFrameSinkVideoCapturer::~ClientFrameSinkVideoCapturer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void ClientFrameSinkVideoCapturer::SetFormat(media::VideoPixelFormat format,
media::ColorSpace color_space) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
format_.emplace(format, color_space);
capturer_->SetFormat(format, color_space);
}
void ClientFrameSinkVideoCapturer::SetMinCapturePeriod(
base::TimeDelta min_capture_period) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
min_capture_period_ = min_capture_period;
capturer_->SetMinCapturePeriod(min_capture_period);
}
void ClientFrameSinkVideoCapturer::SetMinSizeChangePeriod(
base::TimeDelta min_period) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
min_size_change_period_ = min_period;
capturer_->SetMinSizeChangePeriod(min_period);
}
......@@ -48,45 +56,81 @@ void ClientFrameSinkVideoCapturer::SetResolutionConstraints(
const gfx::Size& min_size,
const gfx::Size& max_size,
bool use_fixed_aspect_ratio) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
resolution_constraints_.emplace(min_size, max_size, use_fixed_aspect_ratio);
capturer_->SetResolutionConstraints(min_size, max_size,
use_fixed_aspect_ratio);
}
void ClientFrameSinkVideoCapturer::SetAutoThrottlingEnabled(bool enabled) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto_throttling_enabled_ = enabled;
capturer_->SetAutoThrottlingEnabled(enabled);
}
void ClientFrameSinkVideoCapturer::ChangeTarget(
const base::Optional<FrameSinkId>& frame_sink_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
target_ = frame_sink_id;
capturer_->ChangeTarget(frame_sink_id);
}
void ClientFrameSinkVideoCapturer::Start(
mojom::FrameSinkVideoConsumer* consumer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(consumer);
is_started_ = true;
consumer_ = consumer;
StartInternal();
}
void ClientFrameSinkVideoCapturer::Stop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_started_ = false;
capturer_->Stop();
}
void ClientFrameSinkVideoCapturer::StopAndResetConsumer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Stop();
consumer_ = nullptr;
consumer_binding_.Close();
}
void ClientFrameSinkVideoCapturer::RequestRefreshFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
capturer_->RequestRefreshFrame();
}
std::unique_ptr<ClientFrameSinkVideoCapturer::Overlay>
ClientFrameSinkVideoCapturer::CreateOverlay(int32_t stacking_index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If there is an existing overlay at the same index, drop it.
auto it = std::find_if(overlays_.begin(), overlays_.end(),
[&stacking_index](const Overlay* overlay) {
return overlay->stacking_index() == stacking_index;
});
if (it != overlays_.end()) {
(*it)->DisconnectPermanently();
overlays_.erase(it);
}
auto overlay =
std::make_unique<Overlay>(weak_factory_.GetWeakPtr(), stacking_index);
overlays_.push_back(overlay.get());
if (capturer_)
overlays_.back()->EstablishConnection(capturer_.get());
return overlay;
}
ClientFrameSinkVideoCapturer::Format::Format(
media::VideoPixelFormat pixel_format,
media::ColorSpace color_space)
......@@ -107,15 +151,21 @@ void ClientFrameSinkVideoCapturer::OnFrameCaptured(
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
consumer_->OnFrameCaptured(std::move(buffer), buffer_size, std::move(info),
update_rect, content_rect, std::move(callbacks));
}
void ClientFrameSinkVideoCapturer::OnStopped() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
consumer_->OnStopped();
}
void ClientFrameSinkVideoCapturer::EstablishConnection() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
establish_connection_callback_.Run(mojo::MakeRequest(&capturer_));
capturer_.set_connection_error_handler(
base::BindOnce(&ClientFrameSinkVideoCapturer::OnConnectionError,
......@@ -135,11 +185,15 @@ void ClientFrameSinkVideoCapturer::EstablishConnection() {
capturer_->SetAutoThrottlingEnabled(*auto_throttling_enabled_);
if (target_)
capturer_->ChangeTarget(target_);
for (Overlay* overlay : overlays_)
overlay->EstablishConnection(capturer_.get());
if (is_started_)
StartInternal();
}
void ClientFrameSinkVideoCapturer::OnConnectionError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ClientFrameSinkVideoCapturer::EstablishConnection,
......@@ -155,4 +209,73 @@ void ClientFrameSinkVideoCapturer::StartInternal() {
capturer_->Start(std::move(consumer));
}
void ClientFrameSinkVideoCapturer::OnOverlayDestroyed(Overlay* overlay) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto it = std::find(overlays_.begin(), overlays_.end(), overlay);
DCHECK(it != overlays_.end());
overlays_.erase(it);
}
ClientFrameSinkVideoCapturer::Overlay::Overlay(
base::WeakPtr<ClientFrameSinkVideoCapturer> client_capturer,
int32_t stacking_index)
: client_capturer_(client_capturer), stacking_index_(stacking_index) {}
ClientFrameSinkVideoCapturer::Overlay::~Overlay() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (client_capturer_)
client_capturer_->OnOverlayDestroyed(this);
}
void ClientFrameSinkVideoCapturer::Overlay::SetImageAndBounds(
const SkBitmap& image,
const gfx::RectF& bounds) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!image.isNull());
if (!client_capturer_)
return;
image_ = image;
bounds_ = bounds;
overlay_->SetImageAndBounds(image_, bounds_);
}
void ClientFrameSinkVideoCapturer::Overlay::SetBounds(
const gfx::RectF& bounds) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!client_capturer_)
return;
bounds_ = bounds;
overlay_->SetBounds(bounds_);
}
void ClientFrameSinkVideoCapturer::Overlay::DisconnectPermanently() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
client_capturer_.reset();
overlay_.reset();
image_.reset();
}
void ClientFrameSinkVideoCapturer::Overlay::EstablishConnection(
mojom::FrameSinkVideoCapturer* capturer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(client_capturer_);
capturer->CreateOverlay(stacking_index_, mojo::MakeRequest(&overlay_));
// Note: There's no need to add a connection error handler on the
// InterfacePtr. If the connection to the service is lost, the
// ClientFrameSinkVideoCapturer will realize this when the
// FrameSinkVideoCapturer's binding is lost, and re-establish a connection to
// both that and this overlay.
if (!image_.isNull())
overlay_->SetImageAndBounds(image_, bounds_);
}
} // namespace viz
......@@ -5,13 +5,18 @@
#ifndef COMPONENTS_VIZ_HOST_CLIENT_FRAME_SINK_VIDEO_CAPTURER_H_
#define COMPONENTS_VIZ_HOST_CLIENT_FRAME_SINK_VIDEO_CAPTURER_H_
#include <vector>
#include "base/callback.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/host/viz_host_export.h"
#include "media/base/video_types.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
namespace viz {
......@@ -28,6 +33,37 @@ namespace viz {
class VIZ_HOST_EXPORT ClientFrameSinkVideoCapturer
: private mojom::FrameSinkVideoConsumer {
public:
// A re-connectable FrameSinkVideoCaptureOverlay. See CreateOverlay().
class Overlay : public mojom::FrameSinkVideoCaptureOverlay {
public:
Overlay(base::WeakPtr<ClientFrameSinkVideoCapturer> client_capturer,
int32_t stacking_index);
~Overlay() final;
int32_t stacking_index() const { return stacking_index_; }
// mojom::FrameSinkVideoCaptureOverlay implementation.
void SetImageAndBounds(const SkBitmap& image,
const gfx::RectF& bounds) final;
void SetBounds(const gfx::RectF& bounds) final;
private:
friend class ClientFrameSinkVideoCapturer;
void DisconnectPermanently();
void EstablishConnection(mojom::FrameSinkVideoCapturer* capturer);
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtr<ClientFrameSinkVideoCapturer> client_capturer_;
const int32_t stacking_index_;
mojom::FrameSinkVideoCaptureOverlayPtr overlay_;
SkBitmap image_;
gfx::RectF bounds_;
DISALLOW_COPY_AND_ASSIGN(Overlay);
};
using EstablishConnectionCallback =
base::RepeatingCallback<void(mojom::FrameSinkVideoCapturerRequest)>;
......@@ -55,6 +91,10 @@ class VIZ_HOST_EXPORT ClientFrameSinkVideoCapturer
// messages (even OnStopped()) will be delivered to the consumer.
void StopAndResetConsumer();
// Similar to FrameSinkVideoCapturer::CreateOverlay, except that it returns an
// owned pointer to an Overlay.
std::unique_ptr<Overlay> CreateOverlay(int32_t stacking_index);
private:
struct Format {
Format(media::VideoPixelFormat pixel_format, media::ColorSpace color_space);
......@@ -93,6 +133,10 @@ class VIZ_HOST_EXPORT ClientFrameSinkVideoCapturer
void StartInternal();
void OnOverlayDestroyed(Overlay* overlay);
SEQUENCE_CHECKER(sequence_checker_);
// The following variables keep the latest arguments provided to their
// corresponding method in mojom::FrameSinkVideoCapturer. The arguments are
// saved so we can resend them if viz crashes and a new FrameSinkVideoCapturer
......@@ -103,6 +147,8 @@ class VIZ_HOST_EXPORT ClientFrameSinkVideoCapturer
base::Optional<ResolutionConstraints> resolution_constraints_;
base::Optional<bool> auto_throttling_enabled_;
base::Optional<FrameSinkId> target_;
// Overlays are owned by the callers of CreateOverlay().
std::vector<Overlay*> overlays_;
bool is_started_ = false;
mojom::FrameSinkVideoConsumer* consumer_ = nullptr;
......
......@@ -255,6 +255,17 @@ void FrameSinkVideoCapturerImpl::RequestRefreshFrame() {
}
}
void FrameSinkVideoCapturerImpl::CreateOverlay(
int32_t stacking_index,
mojom::FrameSinkVideoCaptureOverlayRequest request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This will cause an existing overlay with the same stacking index to be
// dropped, per mojom-documented behavior.
overlays_.emplace(stacking_index, std::make_unique<VideoCaptureOverlay>(
this, std::move(request)));
}
void FrameSinkVideoCapturerImpl::ScheduleRefreshFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......@@ -327,7 +338,7 @@ void FrameSinkVideoCapturerImpl::OnFrameDamaged(
DCHECK(resolved_target_);
if (frame_size == oracle_.source_size()) {
dirty_rect_.Union(damage_rect);
InvalidateRect(damage_rect);
} else {
oracle_.SetSourceSize(frame_size);
dirty_rect_ = kMaxRect;
......@@ -337,6 +348,45 @@ void FrameSinkVideoCapturerImpl::OnFrameDamaged(
expected_display_time, frame_metadata);
}
gfx::Size FrameSinkVideoCapturerImpl::GetSourceSize() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return oracle_.source_size();
}
void FrameSinkVideoCapturerImpl::InvalidateRect(const gfx::Rect& rect) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
gfx::Rect positive_rect = rect;
positive_rect.Intersect(kMaxRect);
dirty_rect_.Union(positive_rect);
}
void FrameSinkVideoCapturerImpl::OnOverlayConnectionLost(
VideoCaptureOverlay* overlay) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto it =
std::find_if(overlays_.begin(), overlays_.end(),
[&overlay](const decltype(overlays_)::value_type& entry) {
return entry.second.get() == overlay;
});
DCHECK(it != overlays_.end());
overlays_.erase(it);
}
std::vector<VideoCaptureOverlay*>
FrameSinkVideoCapturerImpl::GetOverlaysInOrder() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<VideoCaptureOverlay*> list;
list.reserve(overlays_.size());
for (const auto& entry : overlays_) {
list.push_back(entry.second.get());
}
return list;
}
void FrameSinkVideoCapturerImpl::MaybeCaptureFrame(
VideoCaptureOracle::Event event,
const gfx::Rect& damage_rect,
......@@ -497,7 +547,11 @@ void FrameSinkVideoCapturerImpl::MaybeCaptureFrame(
: CopyOutputRequest::ResultFormat::RGBA_BITMAP,
base::BindOnce(&FrameSinkVideoCapturerImpl::DidCopyFrame,
capture_weak_factory_.GetWeakPtr(), frame_number,
oracle_frame_number, content_rect, std::move(frame))));
oracle_frame_number, content_rect,
VideoCaptureOverlay::MakeCombinedRenderer(
GetOverlaysInOrder(), content_rect, frame->format(),
frame->ColorSpace()),
std::move(frame))));
request->set_source(copy_request_source_);
request->set_area(gfx::Rect(source_size));
request->SetScaleRatio(
......@@ -515,6 +569,7 @@ void FrameSinkVideoCapturerImpl::DidCopyFrame(
int64_t frame_number,
OracleFrameNumber oracle_frame_number,
const gfx::Rect& content_rect,
VideoCaptureOverlay::OnceRenderer overlay_renderer,
scoped_refptr<VideoFrame> frame,
std::unique_ptr<CopyOutputResult> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......@@ -543,17 +598,7 @@ void FrameSinkVideoCapturerImpl::DidCopyFrame(
uint8_t* const v = frame->visible_data(VideoFrame::kVPlane) +
(content_rect.y() / 2) * v_stride +
(content_rect.x() / 2);
if (result->ReadI420Planes(y, y_stride, u, u_stride, v, v_stride)) {
// The result may be smaller than what was requested, if unforeseen
// clamping to the source boundaries occurred by the executor of the
// CopyOutputRequest. However, the result should never contain more than
// what was requested.
DCHECK_LE(result->size().width(), content_rect.width());
DCHECK_LE(result->size().height(), content_rect.height());
media::LetterboxVideoFrame(
frame.get(), gfx::Rect(content_rect.origin(),
AdjustSizeForPixelFormat(result->size())));
} else {
if (!result->ReadI420Planes(y, y_stride, u, u_stride, v, v_stride)) {
frame = nullptr;
}
} else {
......@@ -561,15 +606,27 @@ void FrameSinkVideoCapturerImpl::DidCopyFrame(
DCHECK_EQ(media::PIXEL_FORMAT_ARGB, pixel_format_);
uint8_t* const pixels = frame->visible_data(VideoFrame::kARGBPlane) +
content_rect.y() * stride + content_rect.x() * 4;
if (result->ReadRGBAPlane(pixels, stride)) {
media::LetterboxVideoFrame(
frame.get(), gfx::Rect(content_rect.origin(),
AdjustSizeForPixelFormat(result->size())));
} else {
if (!result->ReadRGBAPlane(pixels, stride)) {
frame = nullptr;
}
}
if (frame) {
if (overlay_renderer) {
std::move(overlay_renderer).Run(frame.get());
}
// The result may be smaller than what was requested, if unforeseen
// clamping to the source boundaries occurred by the executor of the
// CopyOutputRequest. However, the result should never contain more than
// what was requested.
DCHECK_LE(result->size().width(), content_rect.width());
DCHECK_LE(result->size().height(), content_rect.height());
media::LetterboxVideoFrame(
frame.get(), gfx::Rect(content_rect.origin(),
AdjustSizeForPixelFormat(result->size())));
}
DidCaptureFrame(frame_number, oracle_frame_number, content_rect,
std::move(frame));
}
......
......@@ -9,7 +9,9 @@
#include <memory>
#include <queue>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
......@@ -23,6 +25,7 @@
#include "components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h"
#include "components/viz/service/frame_sinks/video_capture/in_flight_frame_delivery.h"
#include "components/viz/service/frame_sinks/video_capture/interprocess_frame_pool.h"
#include "components/viz/service/frame_sinks/video_capture/video_capture_overlay.h"
#include "components/viz/service/viz_service_export.h"
#include "media/base/video_frame.h"
#include "media/capture/content/video_capture_oracle.h"
......@@ -64,6 +67,7 @@ class FrameSinkVideoCapturerManager;
// memory for efficient transport to the consumer.
class VIZ_SERVICE_EXPORT FrameSinkVideoCapturerImpl final
: public CapturableFrameSink::Client,
public VideoCaptureOverlay::FrameSource,
public mojom::FrameSinkVideoCapturer {
public:
// |frame_sink_manager| must outlive this instance. Binds this instance to the
......@@ -104,6 +108,8 @@ class VIZ_SERVICE_EXPORT FrameSinkVideoCapturerImpl final
void Start(mojom::FrameSinkVideoConsumerPtr consumer) final;
void Stop() final;
void RequestRefreshFrame() final;
void CreateOverlay(int32_t stacking_index,
mojom::FrameSinkVideoCaptureOverlayRequest request) final;
// Default configuration.
static constexpr media::VideoPixelFormat kDefaultPixelFormat =
......@@ -162,6 +168,14 @@ class VIZ_SERVICE_EXPORT FrameSinkVideoCapturerImpl final
base::TimeTicks target_display_time,
const CompositorFrameMetadata& frame_metadata) final;
// VideoCaptureOverlay::FrameSource implementation:
gfx::Size GetSourceSize() final;
void InvalidateRect(const gfx::Rect& rect) final;
void OnOverlayConnectionLost(VideoCaptureOverlay* overlay) final;
// Returns a list of the overlays in rendering order.
std::vector<VideoCaptureOverlay*> GetOverlaysInOrder() const;
// Consults the VideoCaptureOracle to decide whether to capture a frame,
// then ensures prerequisites are met before initiating the capture: that
// there is a consumer present and that the pipeline is not already full.
......@@ -175,6 +189,7 @@ class VIZ_SERVICE_EXPORT FrameSinkVideoCapturerImpl final
void DidCopyFrame(int64_t frame_number,
OracleFrameNumber oracle_frame_number,
const gfx::Rect& content_rect,
VideoCaptureOverlay::OnceRenderer overlay_renderer,
scoped_refptr<media::VideoFrame> frame,
std::unique_ptr<CopyOutputResult> result);
......@@ -278,6 +293,12 @@ class VIZ_SERVICE_EXPORT FrameSinkVideoCapturerImpl final
// compute the relative media stream timestamps for each successive frame.
base::Optional<base::TimeTicks> first_frame_media_ticks_;
// Zero or more overlays to be rendered over each captured video frame. The
// order of the entries in this map determines the order in which each overlay
// is rendered. This is important because alpha blending between overlays can
// make a difference in the overall results.
base::flat_map<int32_t, std::unique_ptr<VideoCaptureOverlay>> overlays_;
// This class assumes its control operations and async callbacks won't execute
// simultaneously.
SEQUENCE_CHECKER(sequence_checker_);
......
......@@ -26,9 +26,14 @@ namespace viz {
VideoCaptureOverlay::FrameSource::~FrameSource() = default;
VideoCaptureOverlay::VideoCaptureOverlay(FrameSource* frame_source)
: frame_source_(frame_source) {
VideoCaptureOverlay::VideoCaptureOverlay(
FrameSource* frame_source,
mojom::FrameSinkVideoCaptureOverlayRequest request)
: frame_source_(frame_source), binding_(this, std::move(request)) {
DCHECK(frame_source_);
binding_.set_connection_error_handler(
base::BindOnce(&FrameSource::OnOverlayConnectionLost,
base::Unretained(frame_source_), this));
}
VideoCaptureOverlay::~VideoCaptureOverlay() = default;
......@@ -160,7 +165,7 @@ VideoCaptureOverlay::OnceRenderer VideoCaptureOverlay::MakeRenderer(
// static
VideoCaptureOverlay::OnceRenderer VideoCaptureOverlay::MakeCombinedRenderer(
const std::vector<std::unique_ptr<VideoCaptureOverlay>>& overlays,
const std::vector<VideoCaptureOverlay*>& overlays,
const gfx::Rect& region_in_frame,
const VideoPixelFormat frame_format,
const gfx::ColorSpace& frame_color_space) {
......@@ -169,7 +174,7 @@ VideoCaptureOverlay::OnceRenderer VideoCaptureOverlay::MakeCombinedRenderer(
}
std::vector<OnceRenderer> renderers;
for (const std::unique_ptr<VideoCaptureOverlay>& overlay : overlays) {
for (VideoCaptureOverlay* overlay : overlays) {
renderers.emplace_back(overlay->MakeRenderer(region_in_frame, frame_format,
frame_color_space));
if (renderers.back().is_null()) {
......
......@@ -16,6 +16,8 @@
#include "base/sequence_checker.h"
#include "components/viz/service/viz_service_export.h"
#include "media/base/video_types.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/color_transform.h"
......@@ -44,10 +46,8 @@ namespace viz {
//
// The blit algorithm uses naive linear blending. Thus, the use of non-linear
// color spaces will cause loses in color accuracy.
//
// TODO(crbug.com/810133): Override the mojom::FrameSinkVideoCaptureOverlay
// interface.
class VIZ_SERVICE_EXPORT VideoCaptureOverlay {
class VIZ_SERVICE_EXPORT VideoCaptureOverlay
: public mojom::FrameSinkVideoCaptureOverlay {
public:
// Interface for notifying the frame source when changes to the overlay's
// state occur.
......@@ -66,6 +66,10 @@ class VIZ_SERVICE_EXPORT VideoCaptureOverlay {
// image and/or position.
virtual void RequestRefreshFrame() = 0;
// Notifies the FrameSource that the VideoCaptureOverlay has lost its mojo
// binding.
virtual void OnOverlayConnectionLost(VideoCaptureOverlay* overlay) = 0;
protected:
virtual ~FrameSource();
};
......@@ -74,18 +78,14 @@ class VIZ_SERVICE_EXPORT VideoCaptureOverlay {
using OnceRenderer = base::OnceCallback<void(media::VideoFrame*)>;
// |frame_source| must outlive this instance.
explicit VideoCaptureOverlay(FrameSource* frame_source);
VideoCaptureOverlay(FrameSource* frame_source,
mojom::FrameSinkVideoCaptureOverlayRequest request);
~VideoCaptureOverlay();
~VideoCaptureOverlay() final;
// Sets/Changes the overlay |image| and its position and size, relative to the
// source content. |bounds| consists of coordinates where the range [0.0,1.0)
// indicates the relative position+size within the bounds of the source
// content (e.g., 0.0 refers to the top or left edge; 1.0 to just after the
// bottom or right edge). Pass empty |bounds| to temporarily hide the overlay
// until a later call to SetBounds().
void SetImageAndBounds(const SkBitmap& image, const gfx::RectF& bounds);
void SetBounds(const gfx::RectF& bounds);
// mojom::FrameSinkVideoCaptureOverlay implementation:
void SetImageAndBounds(const SkBitmap& image, const gfx::RectF& bounds) final;
void SetBounds(const gfx::RectF& bounds) final;
// Returns a OnceCallback that, when run, renders this VideoCaptureOverlay on
// a VideoFrame. The overlay's position and size are computed based on the
......@@ -102,7 +102,7 @@ class VIZ_SERVICE_EXPORT VideoCaptureOverlay {
// deal with collections of callbacks. Returns a null OnceCallback if there is
// nothing to render at this time.
static OnceRenderer MakeCombinedRenderer(
const std::vector<std::unique_ptr<VideoCaptureOverlay>>& overlays,
const std::vector<VideoCaptureOverlay*>& overlays,
const gfx::Rect& region_in_frame,
const media::VideoPixelFormat frame_format,
const gfx::ColorSpace& frame_color_space);
......@@ -165,6 +165,8 @@ class VIZ_SERVICE_EXPORT VideoCaptureOverlay {
FrameSource* const frame_source_;
mojo::Binding<mojom::FrameSinkVideoCaptureOverlay> binding_;
// The currently-set overlay image.
SkBitmap image_;
......
......@@ -52,6 +52,7 @@ class MockFrameSource : public VideoCaptureOverlay::FrameSource {
MOCK_METHOD0(GetSourceSize, gfx::Size());
MOCK_METHOD1(InvalidateRect, void(const gfx::Rect& rect));
MOCK_METHOD0(RequestRefreshFrame, void());
MOCK_METHOD1(OnOverlayConnectionLost, void(VideoCaptureOverlay* overlay));
};
class VideoCaptureOverlayTest : public testing::Test {
......@@ -61,7 +62,8 @@ class VideoCaptureOverlayTest : public testing::Test {
NiceMock<MockFrameSource>* frame_source() { return &frame_source_; }
std::unique_ptr<VideoCaptureOverlay> CreateOverlay() {
return std::make_unique<VideoCaptureOverlay>(frame_source());
return std::make_unique<VideoCaptureOverlay>(
frame_source(), mojom::FrameSinkVideoCaptureOverlayRequest());
}
void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
......@@ -124,6 +126,19 @@ class VideoCaptureOverlayTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(VideoCaptureOverlayTest);
};
// Tests that, when the VideoCaptureOverlay binds to a mojo request, it reports
// when the binding is closed.
TEST_F(VideoCaptureOverlayTest, ReportsLostMojoConnection) {
mojom::FrameSinkVideoCaptureOverlayPtr overlay_ptr;
VideoCaptureOverlay overlay(frame_source(), mojo::MakeRequest(&overlay_ptr));
ASSERT_TRUE(overlay_ptr);
RunUntilIdle(); // Propagate mojo tasks.
EXPECT_CALL(*frame_source(), OnOverlayConnectionLost(&overlay));
overlay_ptr.reset();
RunUntilIdle(); // Propagate mojo tasks.
}
// Tests that MakeRenderer() does not make a OnceRenderer until the client has
// set the image.
TEST_F(VideoCaptureOverlayTest, DoesNotRenderWithoutImage) {
......@@ -176,9 +191,10 @@ TEST_F(VideoCaptureOverlayTest,
DoesNotDoCombinedRenderIfNoOverlaysWouldRender) {
constexpr gfx::Size kSize = gfx::Size(100, 75);
EXPECT_CALL(*frame_source(), GetSourceSize()).WillRepeatedly(Return(kSize));
std::vector<std::unique_ptr<VideoCaptureOverlay>> overlays;
overlays.emplace_back(CreateOverlay());
overlays.emplace_back(CreateOverlay());
const std::unique_ptr<VideoCaptureOverlay> overlay0 = CreateOverlay();
const std::unique_ptr<VideoCaptureOverlay> overlay1 = CreateOverlay();
const std::vector<VideoCaptureOverlay*> overlays{overlay0.get(),
overlay1.get()};
// Neither overlay has an image yet, so the combined renderer should be null.
constexpr gfx::Rect kRegionInFrame = gfx::Rect(kSize);
......@@ -390,7 +406,8 @@ constexpr gfx::Size VideoCaptureOverlayRenderTest::kSourceSize;
// not scaled.
TEST_P(VideoCaptureOverlayRenderTest, FullCover_NoScaling) {
StrictMock<MockFrameSource> frame_source;
VideoCaptureOverlay overlay(&frame_source);
VideoCaptureOverlay overlay(&frame_source,
mojom::FrameSinkVideoCaptureOverlayRequest());
EXPECT_CALL(frame_source, GetSourceSize())
.WillRepeatedly(Return(kSourceSize));
......@@ -414,7 +431,8 @@ TEST_P(VideoCaptureOverlayRenderTest, FullCover_NoScaling) {
// scaled.
TEST_P(VideoCaptureOverlayRenderTest, FullCover_WithScaling) {
StrictMock<MockFrameSource> frame_source;
VideoCaptureOverlay overlay(&frame_source);
VideoCaptureOverlay overlay(&frame_source,
mojom::FrameSinkVideoCaptureOverlayRequest());
EXPECT_CALL(frame_source, GetSourceSize())
.WillRepeatedly(Return(kSourceSize));
......@@ -441,7 +459,8 @@ TEST_P(VideoCaptureOverlayRenderTest, MovesAround) {
NiceMock<MockFrameSource> frame_source;
EXPECT_CALL(frame_source, GetSourceSize())
.WillRepeatedly(Return(kSourceSize));
VideoCaptureOverlay overlay(&frame_source);
VideoCaptureOverlay overlay(&frame_source,
mojom::FrameSinkVideoCaptureOverlayRequest());
const SkBitmap test_bitmap = MakeTestBitmap(0);
const gfx::Size frame_size(test_bitmap.width() * 4, test_bitmap.height() * 4);
......@@ -506,7 +525,8 @@ TEST_P(VideoCaptureOverlayRenderTest, ClipsToContentBounds) {
NiceMock<MockFrameSource> frame_source;
EXPECT_CALL(frame_source, GetSourceSize())
.WillRepeatedly(Return(kSourceSize));
VideoCaptureOverlay overlay(&frame_source);
VideoCaptureOverlay overlay(&frame_source,
mojom::FrameSinkVideoCaptureOverlayRequest());
const SkBitmap test_bitmap = MakeTestBitmap(0);
const gfx::Size frame_size(test_bitmap.width() * 4, test_bitmap.height() * 4);
......
......@@ -91,6 +91,9 @@ class MockFrameSinkVideoCapturer : public viz::mojom::FrameSinkVideoCapturer {
}
MOCK_METHOD0(MockStop, void());
MOCK_METHOD0(RequestRefreshFrame, void());
MOCK_METHOD2(CreateOverlay,
void(int32_t stacking_index,
viz::mojom::FrameSinkVideoCaptureOverlayRequest request));
// Const accessors to get the cached variables.
base::TimeDelta min_capture_period() const { return min_capture_period_; }
......
......@@ -110,6 +110,9 @@ class MockFrameSinkVideoCapturer : public viz::mojom::FrameSinkVideoCapturer {
}
MOCK_METHOD0(MockStop, void());
MOCK_METHOD0(RequestRefreshFrame, void());
MOCK_METHOD2(CreateOverlay,
void(int32_t stacking_index,
viz::mojom::FrameSinkVideoCaptureOverlayRequest request));
private:
mojo::Binding<viz::mojom::FrameSinkVideoCapturer> binding_;
......
......@@ -140,6 +140,13 @@ void LameWindowCapturerChromeOS::RequestRefreshFrame() {
// continuously.
}
void LameWindowCapturerChromeOS::CreateOverlay(
int32_t stacking_index,
viz::mojom::FrameSinkVideoCaptureOverlayRequest request) {
// TODO(crbug.com/810133): Provide an implementation.
NOTIMPLEMENTED();
}
class LameWindowCapturerChromeOS::InFlightFrame
: public viz::mojom::FrameSinkVideoConsumerFrameCallbacks {
public:
......
......@@ -66,6 +66,9 @@ class LameWindowCapturerChromeOS : public viz::mojom::FrameSinkVideoCapturer,
void Start(viz::mojom::FrameSinkVideoConsumerPtr consumer) final;
void Stop() final;
void RequestRefreshFrame() final;
void CreateOverlay(
int32_t stacking_index,
viz::mojom::FrameSinkVideoCaptureOverlayRequest request) final;
private:
// Represents an in-flight frame, being populated by this capturer and then
......
......@@ -20,6 +20,7 @@ mojom("compositing") {
"//media/mojo/interfaces:interfaces",
"//mojo/public/mojom/base",
"//services/viz/public/interfaces",
"//skia/public/interfaces",
"//ui/gfx/geometry/mojo",
"//ui/gfx/mojo",
"//ui/latency/mojo:interfaces",
......
......@@ -8,6 +8,7 @@ import "media/capture/mojom/video_capture_types.mojom";
import "media/mojo/interfaces/media_types.mojom";
import "mojo/public/mojom/base/time.mojom";
import "services/viz/public/interfaces/compositing/frame_sink_id.mojom";
import "skia/public/interfaces/bitmap.mojom";
import "ui/gfx/geometry/mojo/geometry.mojom";
// Provided with each call to FrameSinkVideoConsumer::OnFrameCaptured() so that
......@@ -129,4 +130,30 @@ interface FrameSinkVideoCapturer {
// Requests the capturer send a duplicate of the last frame. This is used to
// resolve occasional "picture loss" issues consumer-side.
RequestRefreshFrame();
// Creates an overlay to be renderered within each captured video frame. The
// |stacking_index| is an arbitrary value that determines whether to render
// this overlay before/after other overlays. Greater values mean "after" and
// "on top of" those with lesser values. Specifying the same index as an
// existing overlay will cause the existing one to be dropped and replaced
// with a new one.
CreateOverlay(int32 stacking_index, FrameSinkVideoCaptureOverlay& request);
};
// Control interface for a small image to be composited on top of each captured
// video frame. This allows clients to, for example, have the capturer render
// mouse cursors or debug info boxes on top of the captured content.
interface FrameSinkVideoCaptureOverlay {
// Sets/Changes the overlay |image| and its position and size, relative to the
// source content. |bounds| consists of coordinates where the range [0.0,1.0)
// indicates the relative position+size within the bounds of the source
// content (e.g., 0.0 refers to the top or left edge; 1.0 to just after the
// bottom or right edge). Pass empty |bounds| to temporarily hide the overlay
// until a later call to SetBounds().
SetImageAndBounds(skia.mojom.Bitmap image, gfx.mojom.RectF bounds);
// Changes the bounds of the previously-set image, showing the overlay if
// non-empty bounds are provided, and hiding the overlay otherwise. |bounds|
// has the same semantics as described in ShowImageAt().
SetBounds(gfx.mojom.RectF bounds);
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment