| // 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 "chromeos/ash/services/recording/video_capture_params.h" |
| |
| #include "base/check.h" |
| #include "base/notreached.h" |
| #include "chromeos/ash/services/recording/recording_service_constants.h" |
| #include "components/viz/common/surfaces/subtree_capture_id.h" |
| #include "media/base/video_types.h" |
| #include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h" |
| #include "ui/gfx/geometry/dip_util.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| |
| namespace recording { |
| |
| namespace { |
| |
| // Returns a rect that is the result of intersecting the given two rects. |
| gfx::Rect GetIntersectionRect(const gfx::Rect& rect_a, |
| const gfx::Rect& rect_b) { |
| auto result = rect_a; |
| result.Intersect(rect_b); |
| return result; |
| } |
| |
| inline gfx::Size GetSizeInPixels(const gfx::Size& size_dips, float dsf) { |
| return gfx::ToFlooredSize(gfx::ConvertSizeToPixels(size_dips, dsf)); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // FullscreenCaptureParams: |
| |
| class FullscreenCaptureParams : public VideoCaptureParams { |
| public: |
| FullscreenCaptureParams(viz::FrameSinkId frame_sink_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor) |
| : VideoCaptureParams(frame_sink_id, |
| viz::SubtreeCaptureId(), |
| frame_sink_size_dip, |
| device_scale_factor), |
| initial_frame_sink_size_pixels_(current_frame_sink_size_pixels_) {} |
| FullscreenCaptureParams(const FullscreenCaptureParams&) = delete; |
| FullscreenCaptureParams& operator=(const FullscreenCaptureParams&) = delete; |
| ~FullscreenCaptureParams() override = default; |
| |
| // VideoCaptureParams: |
| gfx::Size GetVideoSize() const override { |
| return initial_frame_sink_size_pixels_; |
| } |
| |
| protected: |
| void SetCapturerResolutionConstraints( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer) |
| const override { |
| DCHECK(capturer); |
| |
| capturer->SetResolutionConstraints( |
| /*min_size=*/initial_frame_sink_size_pixels_, |
| /*max_size=*/initial_frame_sink_size_pixels_, |
| /*use_fixed_aspect_ratio=*/true); |
| } |
| |
| bool OnVideoSizeMayHaveChanged( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer) override { |
| // We override the default behavior, as we want the video size to remain at |
| // the original requested size. This gives a nice indication of display |
| // rotations. The new video frames will letterbox to adhere to |
| // the original requested resolution constraints. |
| return false; |
| } |
| |
| private: |
| // We chose not to change the video size for fullscreen recording and let the |
| // capturer letterbox the content in the initial size we gave it. This gives |
| // a nice effect especially when rotating the display. See |
| // OnVideoSizeMayHaveChanged() above. |
| const gfx::Size initial_frame_sink_size_pixels_; |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| // WindowCaptureParams: |
| |
| class WindowCaptureParams : public VideoCaptureParams { |
| public: |
| WindowCaptureParams(viz::FrameSinkId frame_sink_id, |
| viz::SubtreeCaptureId subtree_capture_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor, |
| const gfx::Size& window_size_dip) |
| : VideoCaptureParams(frame_sink_id, |
| subtree_capture_id, |
| frame_sink_size_dip, |
| device_scale_factor), |
| current_window_size_dips_(window_size_dip), |
| current_window_size_pixels_(CalculateWindowSizeInPixels()) {} |
| WindowCaptureParams(const WindowCaptureParams&) = delete; |
| WindowCaptureParams& operator=(const WindowCaptureParams&) = delete; |
| ~WindowCaptureParams() override = default; |
| |
| // VideoCaptureParams: |
| gfx::Rect GetVideoFrameVisibleRect( |
| const gfx::Rect& original_frame_visible_rect_pixels) const override { |
| return GetIntersectionRect(original_frame_visible_rect_pixels, |
| gfx::Rect(current_window_size_pixels_)); |
| } |
| |
| gfx::Size GetVideoSize() const override { |
| return current_window_size_pixels_; |
| } |
| |
| bool OnRecordedWindowChangingRoot( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer, |
| viz::FrameSinkId new_frame_sink_id, |
| const gfx::Size& new_frame_sink_size_dip, |
| float new_device_scale_factor) override { |
| DCHECK(new_frame_sink_id.is_valid()); |
| DCHECK_NE(frame_sink_id_, new_frame_sink_id); |
| |
| frame_sink_id_ = new_frame_sink_id; |
| capturer->ChangeTarget( |
| viz::VideoCaptureTarget(frame_sink_id_, subtree_capture_id_), |
| /*sub_capture_target_version=*/0); |
| |
| // If the movement to another display results in changes in the frame sink |
| // size or DSF, OnVideoSizeMayHaveChanged() will be called by the below |
| // OnFrameSinkSizeChanged(). |
| return OnFrameSinkSizeChanged(capturer, new_frame_sink_size_dip, |
| new_device_scale_factor); |
| } |
| |
| bool OnRecordedWindowSizeChanged( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer, |
| const gfx::Size& new_window_size_dip) override { |
| current_window_size_dips_ = new_window_size_dip; |
| return OnVideoSizeMayHaveChanged(capturer); |
| } |
| |
| protected: |
| void SetCapturerResolutionConstraints( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer) |
| const override { |
| DCHECK(capturer); |
| |
| // To avoid receiving letterboxed video frames from the capturer, we ask it |
| // to give us an exact resolution matching the window's size in pixels. |
| capturer->SetResolutionConstraints(/*min_size=*/current_window_size_pixels_, |
| /*max_size=*/current_window_size_pixels_, |
| /*use_fixed_aspect_ratio=*/true); |
| } |
| |
| bool OnVideoSizeMayHaveChanged( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer) override { |
| const auto new_window_size_pixels = CalculateWindowSizeInPixels(); |
| if (new_window_size_pixels == current_window_size_pixels_) |
| return false; |
| current_window_size_pixels_ = new_window_size_pixels; |
| SetCapturerResolutionConstraints(capturer); |
| return true; |
| } |
| |
| private: |
| gfx::Size CalculateWindowSizeInPixels() const { |
| return GetSizeInPixels(current_window_size_dips_, |
| current_device_scale_factor_); |
| } |
| |
| gfx::Size current_window_size_dips_; |
| gfx::Size current_window_size_pixels_; |
| }; |
| |
| // ----------------------------------------------------------------------------- |
| // RegionCaptureParams: |
| |
| class RegionCaptureParams : public VideoCaptureParams { |
| public: |
| RegionCaptureParams(viz::FrameSinkId frame_sink_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor, |
| const gfx::Rect& crop_region_dip) |
| : VideoCaptureParams(frame_sink_id, |
| viz::SubtreeCaptureId(), |
| frame_sink_size_dip, |
| device_scale_factor), |
| crop_region_dips_(crop_region_dip), |
| crop_region_pixels_(CalculateCropRegionInPixels()) { |
| DCHECK(!crop_region_dips_.IsEmpty()); |
| DCHECK(!crop_region_pixels_.IsEmpty()); |
| } |
| RegionCaptureParams(const RegionCaptureParams&) = delete; |
| RegionCaptureParams& operator=(const RegionCaptureParams&) = delete; |
| ~RegionCaptureParams() override = default; |
| |
| // VideoCaptureParams: |
| gfx::Rect GetVideoFrameVisibleRect( |
| const gfx::Rect& original_frame_visible_rect_pixels) const override { |
| // We can't crop the video frame by an invalid bounds. The crop bounds must |
| // be contained within the original frame bounds. |
| gfx::Rect visible_rect = crop_region_pixels_; |
| visible_rect.AdjustToFit(original_frame_visible_rect_pixels); |
| return visible_rect; |
| } |
| |
| gfx::Size GetVideoSize() const override { |
| return GetVideoFrameVisibleRect(gfx::Rect(current_frame_sink_size_pixels_)) |
| .size(); |
| } |
| |
| protected: |
| void SetCapturerResolutionConstraints( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer) |
| const override { |
| DCHECK(capturer); |
| |
| capturer->SetResolutionConstraints( |
| /*min_size=*/current_frame_sink_size_pixels_, |
| /*max_size=*/current_frame_sink_size_pixels_, |
| /*use_fixed_aspect_ratio=*/true); |
| } |
| |
| bool OnVideoSizeMayHaveChanged( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer) override { |
| SetCapturerResolutionConstraints(capturer); |
| const auto new_crop_region_pixels = CalculateCropRegionInPixels(); |
| if (new_crop_region_pixels == crop_region_pixels_) |
| return false; |
| crop_region_pixels_ = new_crop_region_pixels; |
| return true; |
| } |
| |
| private: |
| // Computes and returns the crop region bounds in pixels, according to the |
| // |crop_region_dips_| and the |current_device_scale_factor_|. |
| gfx::Rect CalculateCropRegionInPixels() const { |
| return gfx::ToRoundedRect(gfx::ConvertRectToPixels( |
| crop_region_dips_, current_device_scale_factor_)); |
| } |
| |
| const gfx::Rect crop_region_dips_; |
| gfx::Rect crop_region_pixels_; |
| }; |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| // VideoCaptureParams: |
| |
| // static |
| std::unique_ptr<VideoCaptureParams> |
| VideoCaptureParams::CreateForFullscreenCapture( |
| viz::FrameSinkId frame_sink_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor) { |
| return std::make_unique<FullscreenCaptureParams>( |
| frame_sink_id, frame_sink_size_dip, device_scale_factor); |
| } |
| |
| // static |
| std::unique_ptr<VideoCaptureParams> VideoCaptureParams::CreateForWindowCapture( |
| viz::FrameSinkId frame_sink_id, |
| viz::SubtreeCaptureId subtree_capture_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor, |
| const gfx::Size& window_size_dip) { |
| return std::make_unique<WindowCaptureParams>( |
| frame_sink_id, subtree_capture_id, frame_sink_size_dip, |
| device_scale_factor, window_size_dip); |
| } |
| |
| // static |
| std::unique_ptr<VideoCaptureParams> VideoCaptureParams::CreateForRegionCapture( |
| viz::FrameSinkId frame_sink_id, |
| const gfx::Size& full_capture_size, |
| float device_scale_factor, |
| const gfx::Rect& crop_region_dip) { |
| return std::make_unique<RegionCaptureParams>( |
| frame_sink_id, full_capture_size, device_scale_factor, crop_region_dip); |
| } |
| |
| void VideoCaptureParams::InitializeVideoCapturer( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer, |
| media::VideoPixelFormat supported_pixel_format) const { |
| DCHECK(capturer); |
| |
| capturer->SetMinCapturePeriod(kMinCapturePeriod); |
| capturer->SetMinSizeChangePeriod(kMinPeriodForResizeThrottling); |
| SetCapturerResolutionConstraints(capturer); |
| capturer->SetAutoThrottlingEnabled(false); |
| // TODO(afakhry): Discuss with //media/ team the implications of color space |
| // conversions. |
| capturer->SetFormat(supported_pixel_format); |
| capturer->ChangeTarget( |
| viz::VideoCaptureTarget(frame_sink_id_, subtree_capture_id_), |
| /*sub_capture_target_version=*/0); |
| } |
| |
| gfx::Rect VideoCaptureParams::GetVideoFrameVisibleRect( |
| const gfx::Rect& original_frame_visible_rect_pixels) const { |
| return original_frame_visible_rect_pixels; |
| } |
| |
| bool VideoCaptureParams::OnRecordedWindowChangingRoot( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer, |
| viz::FrameSinkId new_frame_sink_id, |
| const gfx::Size& new_frame_sink_size_dip, |
| float new_device_scale_factor) { |
| NOTREACHED() << "This can only be called when recording a window"; |
| } |
| |
| bool VideoCaptureParams::OnRecordedWindowSizeChanged( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer, |
| const gfx::Size& new_window_size_dip) { |
| NOTREACHED() << "This can only be called when recording a window"; |
| } |
| |
| bool VideoCaptureParams::OnFrameSinkSizeChanged( |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer>& capturer, |
| const gfx::Size& new_frame_sink_size_dip, |
| float new_device_scale_factor) { |
| if (current_frame_sink_size_dips_ == new_frame_sink_size_dip && |
| current_device_scale_factor_ == new_device_scale_factor) { |
| return false; |
| } |
| |
| current_frame_sink_size_dips_ = new_frame_sink_size_dip; |
| current_device_scale_factor_ = new_device_scale_factor; |
| current_frame_sink_size_pixels_ = CalculateFrameSinkSizeInPixels(); |
| |
| return OnVideoSizeMayHaveChanged(capturer); |
| } |
| |
| VideoCaptureParams::VideoCaptureParams(viz::FrameSinkId frame_sink_id, |
| viz::SubtreeCaptureId subtree_capture_id, |
| const gfx::Size& current_frame_sink_size, |
| float device_scale_factor) |
| : frame_sink_id_(frame_sink_id), |
| subtree_capture_id_(subtree_capture_id), |
| current_frame_sink_size_dips_(current_frame_sink_size), |
| current_device_scale_factor_(device_scale_factor), |
| current_frame_sink_size_pixels_(CalculateFrameSinkSizeInPixels()) { |
| DCHECK(frame_sink_id_.is_valid()); |
| } |
| |
| gfx::Size VideoCaptureParams::CalculateFrameSinkSizeInPixels() const { |
| return GetSizeInPixels(current_frame_sink_size_dips_, |
| current_device_scale_factor_); |
| } |
| |
| } // namespace recording |