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 | |
| 7 | #include "cc/paint/skia_paint_canvas.h" |
| 8 | #include "components/viz/host/host_frame_sink_manager.h" |
| 9 | #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h" |
| 10 | #include "content/browser/compositor/surface_utils.h" |
| 11 | #include "media/base/limits.h" |
| 12 | #include "media/renderers/paint_canvas_video_renderer.h" |
| 13 | |
| 14 | namespace content { |
| 15 | |
| 16 | namespace { |
| 17 | |
| 18 | // Frame capture period is 10 frames per second by default. |
| 19 | constexpr base::TimeDelta kDefaultMinCapturePeriod = |
| 20 | base::TimeDelta::FromMilliseconds(100); |
| 21 | |
| 22 | // Frame size can change every frame. |
| 23 | constexpr base::TimeDelta kDefaultMinPeriod = base::TimeDelta(); |
| 24 | |
| 25 | // Allow variable aspect ratio. |
| 26 | const bool kDefaultUseFixedAspectRatio = false; |
| 27 | |
| 28 | } // namespace |
| 29 | |
| 30 | // static |
| 31 | constexpr gfx::Size DevToolsVideoConsumer::kDefaultMinFrameSize; |
| 32 | |
| 33 | // static |
| 34 | constexpr gfx::Size DevToolsVideoConsumer::kDefaultMaxFrameSize; |
| 35 | |
| 36 | DevToolsVideoConsumer::DevToolsVideoConsumer(OnFrameCapturedCallback callback) |
| 37 | : callback_(std::move(callback)), |
| 38 | min_capture_period_(kDefaultMinCapturePeriod), |
| 39 | min_frame_size_(kDefaultMinFrameSize), |
| 40 | max_frame_size_(kDefaultMaxFrameSize), |
| 41 | binding_(this) {} |
| 42 | |
| 43 | DevToolsVideoConsumer::~DevToolsVideoConsumer() = default; |
| 44 | |
| 45 | // static |
| 46 | SkBitmap DevToolsVideoConsumer::GetSkBitmapFromFrame( |
| 47 | scoped_refptr<media::VideoFrame> frame) { |
| 48 | media::PaintCanvasVideoRenderer renderer; |
| 49 | SkBitmap skbitmap; |
| 50 | skbitmap.allocN32Pixels(frame->visible_rect().width(), |
| 51 | frame->visible_rect().height()); |
| 52 | cc::SkiaPaintCanvas canvas(skbitmap); |
| 53 | renderer.Copy(frame, &canvas, media::Context3D()); |
| 54 | return skbitmap; |
| 55 | } |
| 56 | |
| 57 | void DevToolsVideoConsumer::StartCapture() { |
| 58 | if (capturer_) |
| 59 | return; |
| 60 | InnerStartCapture(CreateCapturer()); |
| 61 | } |
| 62 | |
| 63 | void DevToolsVideoConsumer::StopCapture() { |
| 64 | if (!capturer_) |
| 65 | return; |
| 66 | binding_.Close(); |
| 67 | capturer_->Stop(); |
| 68 | capturer_.reset(); |
| 69 | } |
| 70 | |
| 71 | void DevToolsVideoConsumer::SetFrameSinkId( |
| 72 | const viz::FrameSinkId& frame_sink_id) { |
| 73 | frame_sink_id_ = frame_sink_id; |
| 74 | if (capturer_) |
| 75 | capturer_->ChangeTarget(frame_sink_id_); |
| 76 | } |
| 77 | |
| 78 | void DevToolsVideoConsumer::SetMinCapturePeriod( |
| 79 | base::TimeDelta min_capture_period) { |
| 80 | min_capture_period_ = min_capture_period; |
| 81 | if (capturer_) |
| 82 | capturer_->SetMinCapturePeriod(min_capture_period_); |
| 83 | } |
| 84 | |
| 85 | void DevToolsVideoConsumer::SetMinAndMaxFrameSize(gfx::Size min_frame_size, |
| 86 | gfx::Size max_frame_size) { |
| 87 | DCHECK(IsValidMinAndMaxFrameSize(min_frame_size, max_frame_size)); |
| 88 | min_frame_size_ = min_frame_size; |
| 89 | max_frame_size_ = max_frame_size; |
| 90 | if (capturer_) { |
| 91 | capturer_->SetResolutionConstraints(min_frame_size_, max_frame_size_, |
| 92 | kDefaultUseFixedAspectRatio); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | viz::mojom::FrameSinkVideoCapturerPtrInfo |
| 97 | DevToolsVideoConsumer::CreateCapturer() { |
| 98 | viz::HostFrameSinkManager* const manager = GetHostFrameSinkManager(); |
| 99 | viz::mojom::FrameSinkVideoCapturerPtr capturer; |
| 100 | manager->CreateVideoCapturer(mojo::MakeRequest(&capturer)); |
| 101 | return capturer.PassInterface(); |
| 102 | } |
| 103 | |
| 104 | void DevToolsVideoConsumer::InnerStartCapture( |
| 105 | viz::mojom::FrameSinkVideoCapturerPtrInfo capturer_info) { |
| 106 | capturer_.Bind(std::move(capturer_info)); |
| 107 | |
| 108 | // Give |capturer_| the capture parameters. |
| 109 | capturer_->SetMinCapturePeriod(min_capture_period_); |
| 110 | capturer_->SetMinSizeChangePeriod(kDefaultMinPeriod); |
| 111 | capturer_->SetResolutionConstraints(min_frame_size_, max_frame_size_, |
| 112 | kDefaultUseFixedAspectRatio); |
| 113 | capturer_->ChangeTarget(frame_sink_id_); |
| 114 | |
| 115 | viz::mojom::FrameSinkVideoConsumerPtr consumer; |
| 116 | binding_.Bind(mojo::MakeRequest(&consumer)); |
| 117 | capturer_->Start(std::move(consumer)); |
| 118 | } |
| 119 | |
| 120 | bool DevToolsVideoConsumer::IsValidMinAndMaxFrameSize( |
| 121 | gfx::Size min_frame_size, |
| 122 | gfx::Size max_frame_size) { |
| 123 | // Returns true if |
| 124 | // 0 < |min_frame_size| <= |max_frame_size| <= media::limits::kMaxDimension. |
| 125 | return 0 < min_frame_size.width() && 0 < min_frame_size.height() && |
| 126 | min_frame_size.width() <= max_frame_size.width() && |
| 127 | min_frame_size.height() <= max_frame_size.height() && |
| 128 | max_frame_size.width() <= media::limits::kMaxDimension && |
| 129 | max_frame_size.height() <= media::limits::kMaxDimension; |
| 130 | } |
| 131 | |
| 132 | void DevToolsVideoConsumer::OnFrameCaptured( |
| 133 | mojo::ScopedSharedBufferHandle buffer, |
| 134 | uint32_t buffer_size, |
| 135 | ::media::mojom::VideoFrameInfoPtr info, |
| 136 | const gfx::Rect& update_rect, |
| 137 | const gfx::Rect& content_rect, |
| 138 | viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) { |
| 139 | if (!buffer.is_valid()) |
| 140 | return; |
| 141 | |
| 142 | mojo::ScopedSharedBufferMapping mapping = buffer->Map(buffer_size); |
| 143 | if (!mapping) { |
| 144 | DLOG(ERROR) << "Shared memory mapping failed."; |
| 145 | return; |
| 146 | } |
| 147 | |
| 148 | scoped_refptr<media::VideoFrame> frame; |
| 149 | // Setting |frame|'s visible rect equal to |content_rect| so that only the |
| 150 | // portion of the frame that contain content are used. |
| 151 | frame = media::VideoFrame::WrapExternalData( |
| 152 | info->pixel_format, info->coded_size, info->visible_rect, |
| 153 | info->visible_rect.size(), static_cast<uint8_t*>(mapping.get()), |
| 154 | buffer_size, info->timestamp); |
| 155 | if (!frame) |
| 156 | return; |
| 157 | frame->AddDestructionObserver(base::BindOnce( |
| 158 | [](mojo::ScopedSharedBufferMapping mapping) {}, std::move(mapping))); |
Oksana Zhuravlova | 7bc83d1f | 2018-04-12 21:21:47 | [diff] [blame^] | 159 | frame->metadata()->MergeInternalValuesFrom(info->metadata); |
Shridhar Sundarraj | 2af6bca | 2018-04-12 09:26:20 | [diff] [blame] | 160 | |
| 161 | callback_.Run(std::move(frame)); |
| 162 | } |
| 163 | |
| 164 | void DevToolsVideoConsumer::OnTargetLost( |
| 165 | const viz::FrameSinkId& frame_sink_id) {} |
| 166 | |
| 167 | void DevToolsVideoConsumer::OnStopped() {} |
| 168 | |
| 169 | } // namespace content |