Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "content/browser/devtools/devtools_video_consumer.h" |
| 6 | |
Yuri Wiitala | fc5fe70 | 2018-06-20 06:14:00 | [diff] [blame] | 7 | #include <utility> |
| 8 | |
Sebastien Marchand | f8cbfab | 2019-01-25 16:02:30 | [diff] [blame] | 9 | #include "base/bind.h" |
Yuri Wiitala | 4a74fb0 | 2018-08-29 06:09:35 | [diff] [blame] | 10 | #include "base/memory/shared_memory_mapping.h" |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 11 | #include "cc/paint/skia_paint_canvas.h" |
| 12 | #include "components/viz/host/host_frame_sink_manager.h" |
| 13 | #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h" |
| 14 | #include "content/browser/compositor/surface_utils.h" |
| 15 | #include "media/base/limits.h" |
Takuto Ikuta | a6ea2fa | 2019-01-31 05:42:36 | [diff] [blame] | 16 | #include "media/capture/mojom/video_capture_types.mojom.h" |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 17 | #include "media/renderers/paint_canvas_video_renderer.h" |
| 18 | |
| 19 | namespace content { |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | // Frame capture period is 10 frames per second by default. |
| 24 | constexpr base::TimeDelta kDefaultMinCapturePeriod = |
| 25 | base::TimeDelta::FromMilliseconds(100); |
| 26 | |
| 27 | // Frame size can change every frame. |
| 28 | constexpr base::TimeDelta kDefaultMinPeriod = base::TimeDelta(); |
| 29 | |
| 30 | // Allow variable aspect ratio. |
| 31 | const bool kDefaultUseFixedAspectRatio = false; |
| 32 | |
Saman Sami | a4c3ee7 | 2018-05-25 19:38:07 | [diff] [blame] | 33 | // Creates a ClientFrameSinkVideoCapturer via HostFrameSinkManager. |
| 34 | std::unique_ptr<viz::ClientFrameSinkVideoCapturer> CreateCapturer() { |
| 35 | return GetHostFrameSinkManager()->CreateVideoCapturer(); |
| 36 | } |
| 37 | |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 38 | } // namespace |
| 39 | |
| 40 | // static |
| 41 | constexpr gfx::Size DevToolsVideoConsumer::kDefaultMinFrameSize; |
| 42 | |
| 43 | // static |
| 44 | constexpr gfx::Size DevToolsVideoConsumer::kDefaultMaxFrameSize; |
| 45 | |
| 46 | DevToolsVideoConsumer::DevToolsVideoConsumer(OnFrameCapturedCallback callback) |
| 47 | : callback_(std::move(callback)), |
| 48 | min_capture_period_(kDefaultMinCapturePeriod), |
| 49 | min_frame_size_(kDefaultMinFrameSize), |
Saman Sami | a4c3ee7 | 2018-05-25 19:38:07 | [diff] [blame] | 50 | max_frame_size_(kDefaultMaxFrameSize) {} |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 51 | |
| 52 | DevToolsVideoConsumer::~DevToolsVideoConsumer() = default; |
| 53 | |
| 54 | // static |
| 55 | SkBitmap DevToolsVideoConsumer::GetSkBitmapFromFrame( |
| 56 | scoped_refptr<media::VideoFrame> frame) { |
| 57 | media::PaintCanvasVideoRenderer renderer; |
| 58 | SkBitmap skbitmap; |
| 59 | skbitmap.allocN32Pixels(frame->visible_rect().width(), |
| 60 | frame->visible_rect().height()); |
| 61 | cc::SkiaPaintCanvas canvas(skbitmap); |
Antoine Labour | fee4ae5 | 2019-04-24 23:53:57 | [diff] [blame] | 62 | renderer.Copy(frame, &canvas, nullptr); |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 63 | return skbitmap; |
| 64 | } |
| 65 | |
| 66 | void DevToolsVideoConsumer::StartCapture() { |
| 67 | if (capturer_) |
| 68 | return; |
| 69 | InnerStartCapture(CreateCapturer()); |
| 70 | } |
| 71 | |
| 72 | void DevToolsVideoConsumer::StopCapture() { |
| 73 | if (!capturer_) |
| 74 | return; |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 75 | capturer_.reset(); |
| 76 | } |
| 77 | |
| 78 | void DevToolsVideoConsumer::SetFrameSinkId( |
| 79 | const viz::FrameSinkId& frame_sink_id) { |
| 80 | frame_sink_id_ = frame_sink_id; |
Yuri Wiitala | fc5fe70 | 2018-06-20 06:14:00 | [diff] [blame] | 81 | if (capturer_) { |
| 82 | if (frame_sink_id_.is_valid()) |
| 83 | capturer_->ChangeTarget(frame_sink_id_); |
| 84 | else |
| 85 | capturer_->ChangeTarget(base::nullopt); |
| 86 | } |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 87 | } |
| 88 | |
| 89 | void DevToolsVideoConsumer::SetMinCapturePeriod( |
| 90 | base::TimeDelta min_capture_period) { |
| 91 | min_capture_period_ = min_capture_period; |
| 92 | if (capturer_) |
| 93 | capturer_->SetMinCapturePeriod(min_capture_period_); |
| 94 | } |
| 95 | |
| 96 | void DevToolsVideoConsumer::SetMinAndMaxFrameSize(gfx::Size min_frame_size, |
| 97 | gfx::Size max_frame_size) { |
| 98 | DCHECK(IsValidMinAndMaxFrameSize(min_frame_size, max_frame_size)); |
| 99 | min_frame_size_ = min_frame_size; |
| 100 | max_frame_size_ = max_frame_size; |
| 101 | if (capturer_) { |
| 102 | capturer_->SetResolutionConstraints(min_frame_size_, max_frame_size_, |
| 103 | kDefaultUseFixedAspectRatio); |
| 104 | } |
| 105 | } |
| 106 | |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 107 | void DevToolsVideoConsumer::InnerStartCapture( |
Saman Sami | a4c3ee7 | 2018-05-25 19:38:07 | [diff] [blame] | 108 | std::unique_ptr<viz::ClientFrameSinkVideoCapturer> capturer) { |
| 109 | capturer_ = std::move(capturer); |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 110 | |
| 111 | // Give |capturer_| the capture parameters. |
| 112 | capturer_->SetMinCapturePeriod(min_capture_period_); |
| 113 | capturer_->SetMinSizeChangePeriod(kDefaultMinPeriod); |
| 114 | capturer_->SetResolutionConstraints(min_frame_size_, max_frame_size_, |
| 115 | kDefaultUseFixedAspectRatio); |
Yuri Wiitala | fc5fe70 | 2018-06-20 06:14:00 | [diff] [blame] | 116 | if (frame_sink_id_.is_valid()) |
| 117 | capturer_->ChangeTarget(frame_sink_id_); |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 118 | |
Saman Sami | a4c3ee7 | 2018-05-25 19:38:07 | [diff] [blame] | 119 | capturer_->Start(this); |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 120 | } |
| 121 | |
| 122 | bool DevToolsVideoConsumer::IsValidMinAndMaxFrameSize( |
| 123 | gfx::Size min_frame_size, |
| 124 | gfx::Size max_frame_size) { |
| 125 | // Returns true if |
| 126 | // 0 < |min_frame_size| <= |max_frame_size| <= media::limits::kMaxDimension. |
| 127 | return 0 < min_frame_size.width() && 0 < min_frame_size.height() && |
| 128 | min_frame_size.width() <= max_frame_size.width() && |
| 129 | min_frame_size.height() <= max_frame_size.height() && |
| 130 | max_frame_size.width() <= media::limits::kMaxDimension && |
| 131 | max_frame_size.height() <= media::limits::kMaxDimension; |
| 132 | } |
| 133 | |
| 134 | void DevToolsVideoConsumer::OnFrameCaptured( |
Yuri Wiitala | 4a74fb0 | 2018-08-29 06:09:35 | [diff] [blame] | 135 | base::ReadOnlySharedMemoryRegion data, |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 136 | ::media::mojom::VideoFrameInfoPtr info, |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 137 | const gfx::Rect& content_rect, |
| 138 | viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) { |
Yuri Wiitala | 4a74fb0 | 2018-08-29 06:09:35 | [diff] [blame] | 139 | if (!data.IsValid()) |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 140 | return; |
| 141 | |
Yuri Wiitala | 4a74fb0 | 2018-08-29 06:09:35 | [diff] [blame] | 142 | base::ReadOnlySharedMemoryMapping mapping = data.Map(); |
| 143 | if (!mapping.IsValid()) { |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 144 | DLOG(ERROR) << "Shared memory mapping failed."; |
| 145 | return; |
| 146 | } |
Yuri Wiitala | 4a74fb0 | 2018-08-29 06:09:35 | [diff] [blame] | 147 | if (mapping.size() < |
| 148 | media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size)) { |
| 149 | DLOG(ERROR) << "Shared memory size was less than expected."; |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 150 | return; |
Yuri Wiitala | 4a74fb0 | 2018-08-29 06:09:35 | [diff] [blame] | 151 | } |
| 152 | |
| 153 | // Create a media::VideoFrame that wraps the read-only shared memory data. |
| 154 | // Unfortunately, a deep web of not-const-correct code exists in |
| 155 | // media::VideoFrame and media::PaintCanvasVideoRenderer (see |
| 156 | // GetSkBitmapFromFrame() above). So, the pointer's const attribute must be |
| 157 | // casted away. This is safe since the operating system will page fault if |
| 158 | // there is any attempt downstream to mutate the data. |
| 159 | // |
| 160 | // Setting |frame|'s visible rect equal to |content_rect| so that only the |
| 161 | // portion of the frame that contains content is used. |
| 162 | scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData( |
| 163 | info->pixel_format, info->coded_size, content_rect, content_rect.size(), |
| 164 | const_cast<uint8_t*>(static_cast<const uint8_t*>(mapping.memory())), |
| 165 | mapping.size(), info->timestamp); |
| 166 | if (!frame) { |
| 167 | DLOG(ERROR) << "Unable to create VideoFrame wrapper around the shmem."; |
| 168 | return; |
| 169 | } |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 170 | frame->AddDestructionObserver(base::BindOnce( |
Yuri Wiitala | 4a74fb0 | 2018-08-29 06:09:35 | [diff] [blame] | 171 | [](base::ReadOnlySharedMemoryMapping mapping, |
| 172 | viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) {}, |
| 173 | std::move(mapping), std::move(callbacks))); |
Oksana Zhuravlova | 7bc83d1f | 2018-04-12 21:21:47 | [diff] [blame] | 174 | frame->metadata()->MergeInternalValuesFrom(info->metadata); |
Christian Fremerey | f205b1d | 2018-10-04 20:14:06 | [diff] [blame] | 175 | if (info->color_space.has_value()) |
| 176 | frame->set_color_space(info->color_space.value()); |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 177 | |
| 178 | callback_.Run(std::move(frame)); |
| 179 | } |
| 180 | |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 181 | void DevToolsVideoConsumer::OnStopped() {} |
| 182 | |
| 183 | } // namespace content |