#include "content/browser/devtools/devtools_video_consumer.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/shared_memory_mapping.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/common/surfaces/subtree_capture_id.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "content/browser/compositor/surface_utils.h"
#include "media/base/limits.h"
#include "media/capture/mojom/video_capture_buffer.mojom.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
#include "media/renderers/paint_canvas_video_renderer.h"
namespace content {
namespace {
constexpr base::TimeDelta kDefaultMinCapturePeriod = base::Milliseconds(10);
constexpr base::TimeDelta kDefaultMinPeriod = base::TimeDelta();
const bool kDefaultUseFixedAspectRatio = false;
constexpr media::VideoPixelFormat kDefaultPixelFormat =
media::PIXEL_FORMAT_I420;
std::unique_ptr<viz::ClientFrameSinkVideoCapturer> CreateCapturer() {
return GetHostFrameSinkManager()->CreateVideoCapturer();
}
}
constexpr gfx::Size DevToolsVideoConsumer::kDefaultMinFrameSize;
constexpr gfx::Size DevToolsVideoConsumer::kDefaultMaxFrameSize;
DevToolsVideoConsumer::DevToolsVideoConsumer(OnFrameCapturedCallback callback)
: callback_(std::move(callback)),
min_capture_period_(kDefaultMinCapturePeriod),
min_frame_size_(kDefaultMinFrameSize),
max_frame_size_(kDefaultMaxFrameSize),
pixel_format_(kDefaultPixelFormat) {}
DevToolsVideoConsumer::~DevToolsVideoConsumer() = default;
SkBitmap DevToolsVideoConsumer::GetSkBitmapFromFrame(
scoped_refptr<media::VideoFrame> frame) {
media::PaintCanvasVideoRenderer renderer;
SkBitmap skbitmap;
skbitmap.allocN32Pixels(frame->visible_rect().width(),
frame->visible_rect().height());
cc::SkiaPaintCanvas canvas(skbitmap);
renderer.Copy(frame, &canvas, nullptr);
return skbitmap;
}
void DevToolsVideoConsumer::StartCapture() {
if (capturer_)
return;
InnerStartCapture(CreateCapturer());
}
void DevToolsVideoConsumer::StopCapture() {
if (!capturer_)
return;
capturer_.reset();
}
void DevToolsVideoConsumer::SetFrameSinkId(
const viz::FrameSinkId& frame_sink_id) {
frame_sink_id_ = frame_sink_id;
if (capturer_) {
capturer_->ChangeTarget(viz::VideoCaptureTarget(frame_sink_id_));
}
}
void DevToolsVideoConsumer::SetMinCapturePeriod(
base::TimeDelta min_capture_period) {
min_capture_period_ = min_capture_period;
if (capturer_)
capturer_->SetMinCapturePeriod(min_capture_period_);
}
void DevToolsVideoConsumer::SetMinAndMaxFrameSize(gfx::Size min_frame_size,
gfx::Size max_frame_size) {
DCHECK(IsValidMinAndMaxFrameSize(min_frame_size, max_frame_size));
min_frame_size_ = min_frame_size;
max_frame_size_ = max_frame_size;
if (capturer_) {
capturer_->SetResolutionConstraints(min_frame_size_, max_frame_size_,
kDefaultUseFixedAspectRatio);
}
}
void DevToolsVideoConsumer::SetFormat(media::VideoPixelFormat format) {
pixel_format_ = format;
if (capturer_) {
capturer_->SetFormat(pixel_format_);
}
}
void DevToolsVideoConsumer::InnerStartCapture(
std::unique_ptr<viz::ClientFrameSinkVideoCapturer> capturer) {
capturer_ = std::move(capturer);
capturer_->SetMinCapturePeriod(min_capture_period_);
capturer_->SetMinSizeChangePeriod(kDefaultMinPeriod);
capturer_->SetResolutionConstraints(min_frame_size_, max_frame_size_,
kDefaultUseFixedAspectRatio);
capturer_->SetFormat(pixel_format_);
if (frame_sink_id_.is_valid())
capturer_->ChangeTarget(viz::VideoCaptureTarget(frame_sink_id_));
capturer_->Start(this, viz::mojom::BufferFormatPreference::kDefault);
}
bool DevToolsVideoConsumer::IsValidMinAndMaxFrameSize(
gfx::Size min_frame_size,
gfx::Size max_frame_size) {
return 0 < min_frame_size.width() && 0 < min_frame_size.height() &&
min_frame_size.width() <= max_frame_size.width() &&
min_frame_size.height() <= max_frame_size.height() &&
max_frame_size.width() <= media::limits::kMaxDimension &&
max_frame_size.height() <= media::limits::kMaxDimension;
}
void DevToolsVideoConsumer::OnFrameCaptured(
::media::mojom::VideoBufferHandlePtr data,
::media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& content_rect,
mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
callbacks) {
CHECK(data->is_read_only_shmem_region());
base::ReadOnlySharedMemoryRegion& shmem_region =
data->get_read_only_shmem_region();
DCHECK(shmem_region.IsValid());
base::ReadOnlySharedMemoryMapping mapping = shmem_region.Map();
if (!mapping.IsValid()) {
DLOG(ERROR) << "Shared memory mapping failed.";
return;
}
base::span<const uint8_t> mapping_memory(mapping);
if (mapping_memory.size() <
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size)) {
DLOG(ERROR) << "Shared memory size was less than expected.";
return;
}
scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, content_rect, content_rect.size(),
mapping_memory, info->timestamp);
if (!frame) {
DLOG(ERROR) << "Unable to create VideoFrame wrapper around the shmem.";
return;
}
frame->AddDestructionObserver(base::BindOnce(
[](media::mojom::VideoBufferHandlePtr data,
base::ReadOnlySharedMemoryMapping mapping,
mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
callbacks) {},
std::move(data), std::move(mapping), std::move(callbacks)));
frame->set_metadata(info->metadata);
frame->set_color_space(info->color_space);
callback_.Run(std::move(frame));
}
void DevToolsVideoConsumer::OnStopped() {}
}