chromium/remoting/host/chromeos/frame_sink_desktop_capturer.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/chromeos/frame_sink_desktop_capturer.h"

#include "base/time/time.h"
#include "components/viz/common/surfaces/video_capture_target.h"
#include "media/base/video_types.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "remoting/host/chromeos/ash_proxy.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "ui/display/display.h"

namespace remoting {

namespace {
constexpr int kMaxFrameRate = 60;
constexpr auto kPixelFormat = media::VideoPixelFormat::PIXEL_FORMAT_ARGB;
constexpr bool kAutoThrottle = false;

bool IsEqual(gfx::Size lhs, webrtc::DesktopSize rhs) {
  return (lhs.width() == rhs.width()) && (lhs.height() == rhs.height());
}

}  // namespace

FrameSinkDesktopCapturer::FrameSinkDesktopCapturer()
    : FrameSinkDesktopCapturer(AshProxy::Get()) {}

FrameSinkDesktopCapturer::FrameSinkDesktopCapturer(AshProxy& ash_proxy)
    : ash_(ash_proxy) {
  LOG(INFO) << "CRD: Starting frame sink desktop capturer";
}

FrameSinkDesktopCapturer::~FrameSinkDesktopCapturer() {
  if (video_capturer_) {
    video_capturer_->Stop();
  }
}

void FrameSinkDesktopCapturer::Start(DesktopCapturer::Callback* callback) {
  DCHECK(!callback_) << "Start() can only be called once";
  DCHECK(callback);
  callback_ = callback;

  video_capturer_.emplace(base::BindRepeating(
      &FrameSinkDesktopCapturer::BindRemote, base::Unretained(this)));

  video_capturer_->SetFormat(kPixelFormat);
  video_capturer_->SetMinCapturePeriod(base::Hertz(kMaxFrameRate));
  // Allow changing of resolution at any time, otherwise the capturer would not
  // send us the real resolution of the display if we switch source display
  // multiple times within the `min size change period`.
  video_capturer_->SetMinSizeChangePeriod(base::Seconds(0));
  // Disable auto-throttling so the capturer will always use the real resolution
  // of the display we're capturing.
  video_capturer_->SetAutoThrottlingEnabled(kAutoThrottle);
  if (source_display_id_ == display::kInvalidDisplayId) {
    source_display_id_ = ash_->GetPrimaryDisplayId();
  }
  SelectSource(source_display_id_);
  video_capturer_->Start(&video_consumer_,
                         viz::mojom::BufferFormatPreference::kDefault);
}

void FrameSinkDesktopCapturer::BindRemote(
    mojo::PendingReceiver<FrameSinkVideoCapturer> pending_receiver) {
  DCHECK(callback_) << "BindRemote must be called after Start()";

  ash_->CreateVideoCapturer(std::move(pending_receiver));
}

void FrameSinkDesktopCapturer::CaptureFrame() {
  const display::Display* source = ash_->GetDisplayForId(source_display_id_);
  if (!source) {
    callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
    return;
  }

  std::unique_ptr<webrtc::DesktopFrame> frame =
      video_consumer_.GetLatestFrame(source->bounds().origin());

  if (!frame) {
    callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
    return;
  }
  if (!IsEqual(source->GetSizeInPixel(), frame->size())) {
    SelectSource(source_display_id_);
    callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
    return;
  }

  callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
}

bool FrameSinkDesktopCapturer::GetSourceList(SourceList* sources) {
  NOTREACHED_IN_MIGRATION();
  return false;
}

bool FrameSinkDesktopCapturer::SelectSource(SourceId id) {
  if (!ash_->GetDisplayForId(id)) {
    return false;
  }

  source_display_id_ = id;
  if (!video_capturer_) {
    // SelectSource() will be called again by Start() after creating the
    // capturer.
    return true;
  }

  scoped_window_capture_request_ =
      ash_->MakeDisplayCapturable(source_display_id_);

  video_capturer_->SetResolutionConstraints(
      GetSourceDisplay()->GetSizeInPixel(),
      GetSourceDisplay()->GetSizeInPixel(),
      /*use_fixed_aspect_ratio=*/false);
  video_capturer_->ChangeTarget(
      viz::VideoCaptureTarget(ash_->GetFrameSinkId(source_display_id_),
                              scoped_window_capture_request_.GetCaptureId()),
      /*sub_capture_target_version=*/0);
  return true;
}

const display::Display* FrameSinkDesktopCapturer::GetSourceDisplay() {
  return ash_->GetDisplayForId(source_display_id_);
}

}  // namespace remoting