blob: 7373910b3be30f526446a229c7aaa49618a66846 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.h"
#include <stdint.h>
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "media/capture/mojom/video_capture_buffer.mojom.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "ui/gfx/geometry/skia_conversions.h"
BackgroundThumbnailVideoCapturer::BackgroundThumbnailVideoCapturer(
content::WebContents* contents,
GotFrameCallback got_frame_callback)
: contents_(contents), got_frame_callback_(std::move(got_frame_callback)) {
DCHECK(contents_);
DCHECK(got_frame_callback_);
}
BackgroundThumbnailVideoCapturer::~BackgroundThumbnailVideoCapturer() {
if (video_capturer_) {
Stop();
}
}
void BackgroundThumbnailVideoCapturer::Start(
const ThumbnailCaptureInfo& capture_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (video_capturer_) {
return;
}
content::RenderWidgetHostView* const source_view =
contents_->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetWidget()
->GetView();
if (!source_view) {
return;
}
capture_info_ = capture_info;
{
// Assign IDs to each capture session for tracing. IDs are unique
// throughout the lifetime of this process. Using a static here is
// safe since this is only invoked from the UI thread.
static uint64_t capture_num GUARDED_BY_CONTEXT(sequence_checker_) = 0;
cur_capture_num_ = ++capture_num;
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ui", "Tab.Preview.VideoCapture",
TRACE_ID_LOCAL(cur_capture_num_));
}
start_time_ = base::TimeTicks::Now();
num_received_frames_ = 0;
constexpr int kMaxFrameRate = 2;
video_capturer_ = source_view->CreateVideoCapturer();
video_capturer_->SetResolutionConstraints(capture_info_.target_size,
capture_info_.target_size, false);
video_capturer_->SetAutoThrottlingEnabled(false);
video_capturer_->SetMinSizeChangePeriod(base::TimeDelta());
video_capturer_->SetFormat(media::PIXEL_FORMAT_ARGB);
video_capturer_->SetMinCapturePeriod(base::Seconds(1) / kMaxFrameRate);
video_capturer_->Start(this, viz::mojom::BufferFormatPreference::kDefault);
}
void BackgroundThumbnailVideoCapturer::Stop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!video_capturer_) {
return;
}
video_capturer_->Stop();
video_capturer_.reset();
TRACE_EVENT_NESTABLE_ASYNC_END0("ui", "Tab.Preview.VideoCapture",
TRACE_ID_LOCAL(cur_capture_num_));
start_time_ = base::TimeTicks();
cur_capture_num_ = 0;
}
void BackgroundThumbnailVideoCapturer::OnFrameCaptured(
::media::mojom::VideoBufferHandlePtr data,
::media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& content_rect,
mojo::PendingRemote<::viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
callbacks) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(video_capturer_);
mojo::Remote<::viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
callbacks_remote(std::move(callbacks));
CHECK(data->is_read_only_shmem_region());
base::ReadOnlySharedMemoryRegion& shmem_region =
data->get_read_only_shmem_region();
// The |data| parameter is not nullable and mojo type mapping for
// `base::ReadOnlySharedMemoryRegion` defines that nullable version of it is
// the same type, with null check being equivalent to IsValid() check. Given
// the above, we should never be able to receive a read only shmem region that
// is not valid - mojo will enforce it for us.
DCHECK(shmem_region.IsValid());
// Process captured image.
base::ReadOnlySharedMemoryMapping mapping = shmem_region.Map();
if (!mapping.IsValid()) {
DLOG(ERROR) << "Shared memory mapping failed.";
return;
}
if (mapping.size() <
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size)) {
DLOG(ERROR) << "Shared memory size was less than expected.";
return;
}
TRACE_EVENT_INSTANT1("ui", "Tab.Preview.VideoCaptureFrameReceived",
TRACE_EVENT_SCOPE_THREAD, "frame_number",
num_received_frames_);
++num_received_frames_;
uint64_t frame_id = base::trace_event::GetNextGlobalTraceId();
TRACE_EVENT_WITH_FLOW0("ui", "Tab.Preview.ProcessVideoCaptureFrame", frame_id,
TRACE_EVENT_FLAG_FLOW_OUT);
// The SkBitmap's pixels will be marked as immutable, but the installPixels()
// API requires a non-const pointer. So, cast away the const.
void* const pixels = const_cast<void*>(mapping.memory());
// Call installPixels() with a |releaseProc| that: 1) notifies the capturer
// that this consumer has finished with the frame, and 2) releases the shared
// memory mapping.
struct FramePinner {
// Keeps the shared memory that backs |frame_| mapped.
base::ReadOnlySharedMemoryMapping mapping;
// Prevents FrameSinkVideoCapturer from recycling the shared memory that
// backs |frame_|.
mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
releaser;
};
// Subtract back out the scroll bars if we decided there was enough canvas to
// account for them and still have a decent preview image.
const float scale_ratio = static_cast<float>(content_rect.width()) /
capture_info_.copy_rect.width();
const gfx::Insets original_scroll_insets = capture_info_.scrollbar_insets;
const auto scroll_insets = gfx::Insets::TLBR(
0, 0, std::round(original_scroll_insets.width() * scale_ratio),
std::round(original_scroll_insets.height() * scale_ratio));
gfx::Rect effective_content_rect = content_rect;
effective_content_rect.Inset(scroll_insets);
const gfx::Size bitmap_size(content_rect.right(), content_rect.bottom());
SkBitmap frame;
frame.installPixels(
SkImageInfo::MakeN32(bitmap_size.width(), bitmap_size.height(),
kPremul_SkAlphaType,
info->color_space.ToSkColorSpace()),
pixels,
media::VideoFrame::RowBytes(media::VideoFrame::Plane::kARGB,
info->pixel_format, info->coded_size.width()),
[](void* addr, void* context) {
delete static_cast<FramePinner*>(context);
},
new FramePinner{std::move(mapping), callbacks_remote.Unbind()});
frame.setImmutable();
SkBitmap cropped_frame;
if (!frame.extractSubset(&cropped_frame,
gfx::RectToSkIRect(effective_content_rect))) {
return;
}
got_frame_callback_.Run(cropped_frame, frame_id);
}
void BackgroundThumbnailVideoCapturer::OnNewSubCaptureTargetVersion(
uint32_t sub_capture_target_version) {}
void BackgroundThumbnailVideoCapturer::OnFrameWithEmptyRegionCapture() {}
void BackgroundThumbnailVideoCapturer::OnStopped() {}
void BackgroundThumbnailVideoCapturer::OnLog(const std::string& /*message*/) {}