chromium/ash/capture_mode/fake_camera_device.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 "ash/capture_mode/fake_camera_device.h"

#include <cstring>
#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/notreached.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/ipc/common/surface_handle.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/capture/mojom/video_capture_buffer.mojom.h"
#include "media/capture/video_capture_types.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/aura/env.h"
#include "ui/compositor/compositor.h"

namespace ash {

namespace {

// The next ID to be used for a newly created buffer.
int g_next_buffer_id = 0;

scoped_refptr<gpu::ClientSharedImage> CreateSharedImage(
    const gfx::Size& frame_size) {
  gpu::SharedImageUsageSet shared_image_usage =
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
      gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE |
      gpu::SHARED_IMAGE_USAGE_SCANOUT;
  return aura::Env::GetInstance()
      ->context_factory()
      ->SharedMainThreadRasterContextProvider()
      ->SharedImageInterface()
      ->CreateSharedImage(
          {viz::SinglePlaneFormat::kBGRA_8888, frame_size, gfx::ColorSpace(),
           shared_image_usage, "FakeCameraDevice"},
          gpu::kNullSurfaceHandle, gfx::BufferUsage::SCANOUT_CPU_READ_WRITE);
}

SkRect GetCircleRect(const gfx::Point& center, int radius) {
  return SkRect::MakeLTRB(center.x() - radius, center.y() - radius,
                          center.x() + radius, center.y() + radius);
}

// Draws the content of the produced video frame whose size is the given
// `frame_size` on the given `canvas`.
void DrawFrameOnCanvas(cc::SkiaPaintCanvas&& canvas,
                       const gfx::Size& frame_size) {
  cc::PaintFlags flags;
  flags.setStyle(cc::PaintFlags::kFill_Style);
  const auto canvas_bounds =
      SkRect::MakeIWH(frame_size.width(), frame_size.height());
  flags.setColor(SK_ColorBLUE);
  canvas.drawRect(canvas_bounds, flags);
  flags.setColor(SK_ColorGREEN);
  const auto canvas_center = gfx::Rect(frame_size).CenterPoint();
  const int radius = 20;
  canvas.drawOval(GetCircleRect(canvas_center, radius), flags);
  flags.setColor(SK_ColorRED);
  canvas.drawOval(GetCircleRect(gfx::Point(), radius), flags);
}

// -----------------------------------------------------------------------------
// BufferStrategy:

// Defines an interface for operations that can be done on different buffer
// types.
class BufferStrategy {
 public:
  virtual ~BufferStrategy() = default;

  // Gets the handle of the concrete buffer implementation that can be sent over
  // via mojo.
  virtual media::mojom::VideoBufferHandlePtr GetHandle() const = 0;

  // Draws the contents of a video frame whose size is `frame_size` on the
  // buffer backing this object.
  virtual void DrawFrameOnBuffer(const gfx::Size& frame_size) = 0;
};

// -----------------------------------------------------------------------------
// GpuMemoryBufferStrategy:

// Defines a concrete implementation of `BufferStrategy` which creates a
// `GpuMemoryBuffer` and implements all the operations on it.
class GpuMemoryBufferStrategy : public BufferStrategy {
 public:
  explicit GpuMemoryBufferStrategy(const gfx::Size& frame_size)
      : client_si_(CreateSharedImage(frame_size)) {
    CHECK(client_si_);
  }

  // BufferStrategy:
  media::mojom::VideoBufferHandlePtr GetHandle() const override {
    return media::mojom::VideoBufferHandle::NewGpuMemoryBufferHandle(
        client_si_->CloneGpuMemoryBufferHandle());
  }
  void DrawFrameOnBuffer(const gfx::Size& frame_size) override {
    auto scoped_mapping = client_si_->Map();
    CHECK(scoped_mapping);
    const gfx::Size buffer_size = scoped_mapping->Size();
    uint8_t* data = static_cast<uint8_t*>(scoped_mapping->Memory(0));

    // Clear all the buffer to 0.
    memset(data, 0, scoped_mapping->Stride(0) * buffer_size.height());

    SkBitmap bitmap;
    // Create an `SkImageInfo` with color type `kBGRA_8888_SkColorType` which
    // matches the buffer format `BGRA_8888` of the `gmb_`. This `info` will be
    // used to read the pixels from `bitmap` into the buffer.
    const auto info =
        SkImageInfo::Make(frame_size.width(), frame_size.height(),
                          kBGRA_8888_SkColorType, kPremul_SkAlphaType);
    bitmap.setInfo(info);
    bitmap.setPixels(data);
    DrawFrameOnCanvas(cc::SkiaPaintCanvas(bitmap), frame_size);
  }

 private:
  scoped_refptr<gpu::ClientSharedImage> client_si_;
};

// -----------------------------------------------------------------------------
// SharedMemoryBufferStrategy:

// Defines a concrete implementation of `BufferStrategy` which creates an
// `UnsafeSharedMemoryRegion` and implements all the operations on it.
class SharedMemoryBufferStrategy : public BufferStrategy {
 public:
  explicit SharedMemoryBufferStrategy(const gfx::Size& frame_size)
      : region_(base::UnsafeSharedMemoryRegion::Create(
            media::VideoFrame::AllocationSize(media::PIXEL_FORMAT_ARGB,
                                              frame_size))) {
    DCHECK(region_.IsValid());
  }

  // BufferStrategy:
  media::mojom::VideoBufferHandlePtr GetHandle() const override {
    return media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
        region_.Duplicate());
  }
  void DrawFrameOnBuffer(const gfx::Size& frame_size) override {
    if (!mapping_.IsValid())
      mapping_ = region_.Map();
    DCHECK(mapping_.IsValid());
    uint8_t* buffer_ptr = mapping_.GetMemoryAsSpan<uint8_t>().data();
    const int buffer_size = mapping_.size();
    memset(buffer_ptr, 0, buffer_size);
    SkBitmap bitmap;
    bitmap.setInfo(
        SkImageInfo::MakeN32Premul(frame_size.width(), frame_size.height()));
    bitmap.setPixels(buffer_ptr);
    DrawFrameOnCanvas(cc::SkiaPaintCanvas(bitmap), frame_size);
  }

 private:
  base::UnsafeSharedMemoryRegion region_;
  base::WritableSharedMemoryMapping mapping_;
};

}  // namespace

// -----------------------------------------------------------------------------
// FakeCameraDevice::Buffer:

class FakeCameraDevice::Buffer {
 public:
  Buffer(const Buffer&) = delete;
  Buffer& operator=(const Buffer&) = delete;
  ~Buffer() = default;

  // Creates an instance of this object with the given `buffer_id`. The actual
  // underlying buffer will depend on the given `buffer_type` and will be big
  // enough to hold the content of a video frame of size `frame_size`.
  static std::unique_ptr<FakeCameraDevice::Buffer> Create(
      int buffer_id,
      media::VideoCaptureBufferType buffer_type,
      const gfx::Size& frame_size) {
    switch (buffer_type) {
      case media::VideoCaptureBufferType::kSharedMemory:
        return base::WrapUnique(new Buffer(
            buffer_id, buffer_type, frame_size,
            std::make_unique<SharedMemoryBufferStrategy>(frame_size)));
      case media::VideoCaptureBufferType::kGpuMemoryBuffer:
        return base::WrapUnique(
            new Buffer(buffer_id, buffer_type, frame_size,
                       std::make_unique<GpuMemoryBufferStrategy>(frame_size)));
      default:
        NOTREACHED();
    }
  }

  int buffer_id() const { return buffer_id_; }
  media::VideoCaptureBufferType buffer_type() const { return buffer_type_; }
  const gfx::Size& frame_size() const { return frame_size_; }
  void set_is_in_use(bool value) { is_in_use_ = value; }

  // Returns true if this buffer is not already in use, and can be reused for
  // the given `buffer_type` and the given `frame_size`.
  bool CanBeReused(media::VideoCaptureBufferType buffer_type,
                   const gfx::Size& frame_size) const {
    return !is_in_use_ && buffer_type_ == buffer_type &&
           frame_size_ == frame_size;
  }

  // Returns a handle for the underlying buffer that can be sent via mojo.
  media::mojom::VideoBufferHandlePtr GetHandle() const {
    return buffer_strategy_->GetHandle();
  }

  // Draw the content of the video frame on the underlying buffer.
  void DrawFrame() {
    // buffer_strategy_->CopyBitmapToBuffer(
    //     FakeCameraDevice::GetProducedFrameAsBitmap(frame_size_));
    buffer_strategy_->DrawFrameOnBuffer(frame_size_);
  }

 private:
  Buffer(int buffer_id,
         media::VideoCaptureBufferType buffer_type,
         const gfx::Size& frame_size,
         std::unique_ptr<BufferStrategy> strategy)
      : buffer_id_(buffer_id),
        buffer_type_(buffer_type),
        frame_size_(frame_size),
        buffer_strategy_(std::move(strategy)) {}

  // The ID of this buffer.
  const int buffer_id_;

  // The type of this buffer. Only `kSharedMemory`, and `kGpuMemoryBuffer` are
  // supported.
  const media::VideoCaptureBufferType buffer_type_;

  // The size of the video frame this buffer can hold.
  const gfx::Size frame_size_;

  // The strategy object that holds the underlying buffer.
  const std::unique_ptr<BufferStrategy> buffer_strategy_;

  // True if this buffer is still in use by the subscriber.
  bool is_in_use_ = false;
};

// -----------------------------------------------------------------------------
// FakeCameraDevice::Subscription:

// A fake implementation of the `PushVideoStreamSubscription` mojo API which
// is created for a remote implementation of `VideoFrameHandler` that subscribes
// to the camera device.
class FakeCameraDevice::Subscription
    : public video_capture::mojom::PushVideoStreamSubscription {
 public:
  Subscription(
      FakeCameraDevice* owner_device,
      mojo::PendingReceiver<video_capture::mojom::PushVideoStreamSubscription>
          subscription,
      mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber)
      : owner_device_(owner_device),
        receiver_(this, std::move(subscription)),
        subscriber_(std::move(subscriber)) {
    subscriber_.set_disconnect_handler(base::BindOnce(
        &Subscription::OnSubscriberDisconnected, base::Unretained(this)));
  }

  bool is_active() const { return is_active_; }
  bool is_suspended() const { return is_suspended_; }

  void OnNewBuffer(int buffer_id,
                   media::mojom::VideoBufferHandlePtr buffer_handle) {
    DCHECK(is_active_);
    subscriber_->OnNewBuffer(buffer_id, std::move(buffer_handle));
  }

  void OnBufferRetired(int buffer_id) {
    DCHECK(is_active_);
    subscriber_->OnBufferRetired(buffer_id);
  }

  void OnFrameReadyInBuffer(
      video_capture::mojom::ReadyFrameInBufferPtr buffer) {
    DCHECK(is_active_ && !is_suspended_);
    subscriber_->OnFrameReadyInBuffer(std::move(buffer));
  }

  void OnFrameDropped() {
    DCHECK(is_active_ && !is_suspended_);
    subscriber_->OnFrameDropped(
        media::VideoCaptureFrameDropReason::kBufferPoolMaxBufferCountExceeded);
  }

  void TriggerFatalError() {
    subscriber_->OnError(
        media::VideoCaptureError::kCrosHalV3DeviceDelegateMojoConnectionError);
  }

  // video_capture::mojom::PushVideoStreamSubscription:
  void Activate() override {
    is_active_ = true;
    DCHECK(subscriber_);
    subscriber_->OnFrameAccessHandlerReady(
        owner_device_->GetNewAccessHandlerRemote());
    owner_device_->OnSubscriptionActivationChanged(this);
  }
  void Suspend(SuspendCallback callback) override { is_suspended_ = true; }
  void Resume() override { is_suspended_ = false; }
  void GetPhotoState(GetPhotoStateCallback callback) override {}
  void SetPhotoOptions(media::mojom::PhotoSettingsPtr settings,
                       SetPhotoOptionsCallback callback) override {}
  void TakePhoto(TakePhotoCallback callback) override {}
  void Close(CloseCallback callback) override {
    is_active_ = false;
    owner_device_->OnSubscriptionActivationChanged(this);
  }
  void ProcessFeedback(const media::VideoCaptureFeedback& feedback) override {}

 private:
  void OnSubscriberDisconnected() {
    owner_device_->OnSubscriberDisconnected(this);
    // `this` is deleted at this point.
  }

  // The camera device which owns this object.
  const raw_ptr<FakeCameraDevice> owner_device_;

  mojo::Receiver<video_capture::mojom::PushVideoStreamSubscription> receiver_{
      this};

  // The remote subscriber which implements the `VideoFrameHandler` mojo API.
  mojo::Remote<video_capture::mojom::VideoFrameHandler> subscriber_;

  // True when this subscription is active. Active subscriptions always produce
  // `OnNewBuffer()` and `OnBufferRetired()` calls regardless of the state of
  // `is_suspended_`.
  bool is_active_ = false;

  // True if this subscription is suspended. Suspended subscriptions don't
  // produce `OnFrameReadyInBuffer()` nor `OnFrameDropped()` calls.
  bool is_suspended_ = false;
};

// -----------------------------------------------------------------------------
// FakeCameraDevice:

FakeCameraDevice::FakeCameraDevice(
    const media::VideoCaptureDeviceInfo& device_info)
    : device_info_(device_info) {}

FakeCameraDevice::~FakeCameraDevice() = default;

// static
SkBitmap FakeCameraDevice::GetProducedFrameAsBitmap(
    const gfx::Size& frame_size) {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(frame_size.width(), frame_size.height());
  DrawFrameOnCanvas(cc::SkiaPaintCanvas(bitmap), frame_size);
  return bitmap;
}

void FakeCameraDevice::Bind(
    mojo::PendingReceiver<video_capture::mojom::VideoSource> pending_receiver) {
  video_source_receiver_set_.Add(this, std::move(pending_receiver));
}

void FakeCameraDevice::TriggerFatalError() {
  for (auto& pair : subscriptions_map_) {
    auto* subscription = pair.first;
    subscription->TriggerFatalError();
  }
}

void FakeCameraDevice::CreatePushSubscription(
    mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber,
    const media::VideoCaptureParams& requested_settings,
    bool force_reopen_with_new_settings,
    mojo::PendingReceiver<video_capture::mojom::PushVideoStreamSubscription>
        subscription,
    CreatePushSubscriptionCallback callback) {
  auto new_subscription = std::make_unique<FakeCameraDevice::Subscription>(
      this, std::move(subscription), std::move(subscriber));
  auto* new_subscription_ptr = new_subscription.get();
  subscriptions_map_.emplace(new_subscription_ptr, std::move(new_subscription));

  DCHECK(requested_settings.IsValid());
  const bool has_current_settings = current_settings_.has_value();
  if (force_reopen_with_new_settings || !has_current_settings) {
    RetireAllBuffers();
    current_settings_.emplace(requested_settings);
    OnSubscriptionActivationChanged(/*subscription=*/nullptr);
  }

  DCHECK(callback);
  std::move(callback).Run(
      video_capture::mojom::CreatePushSubscriptionResultCode::NewSuccessCode(
          video_capture::mojom::CreatePushSubscriptionSuccessCode::
              kCreatedWithRequestedSettings),
      requested_settings);
}

void FakeCameraDevice::RegisterVideoEffectsProcessor(
    mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor> remote) {}

void FakeCameraDevice::OnFinishedConsumingBuffer(int32_t buffer_id) {
  auto iter = buffer_pool_.find(buffer_id);
  if (iter != buffer_pool_.end())
    iter->second->set_is_in_use(false);
}

mojo::PendingRemote<video_capture::mojom::VideoFrameAccessHandler>
FakeCameraDevice::GetNewAccessHandlerRemote() {
  mojo::PendingReceiver<video_capture::mojom::VideoFrameAccessHandler> receiver;
  auto remote = receiver.InitWithNewPipeAndPassRemote();
  access_handler_receiver_set_.Add(this, std::move(receiver));
  return remote;
}

void FakeCameraDevice::OnSubscriberDisconnected(Subscription* subscription) {
  DCHECK(subscriptions_map_.contains(subscription));

  subscriptions_map_.erase(subscription);

  if (subscriptions_map_.empty()) {
    frame_timer_.Stop();
    current_settings_.reset();
    RetireAllBuffers();
  }
}

void FakeCameraDevice::OnSubscriptionActivationChanged(
    Subscription* subscription) {
  DCHECK(current_settings_.has_value());

  // Newly activated subscriptions should be told about all existing buffers.
  if (subscription && subscription->is_active()) {
    for (auto& pair : buffer_pool_) {
      Buffer* buffer = pair.second.get();
      subscription->OnNewBuffer(buffer->buffer_id(), buffer->GetHandle());
    }
  }

  const auto frame_duration =
      base::Hertz(current_settings_->requested_format.frame_rate);
  if (IsAnySubscriptionActive()) {
    if (!frame_timer_.IsRunning() ||
        frame_timer_.GetCurrentDelay() != frame_duration) {
      frame_timer_.Start(FROM_HERE, frame_duration, this,
                         &FakeCameraDevice::OnNextFrame);
    }
  } else {
    frame_timer_.Stop();
  }
}

bool FakeCameraDevice::IsAnySubscriptionActive() const {
  for (const auto& pair : subscriptions_map_) {
    if (pair.first->is_active())
      return true;
  }
  return false;
}

void FakeCameraDevice::OnNextFrame() {
  DCHECK(IsAnySubscriptionActive());
  DCHECK(current_settings_.has_value());

  if (start_time_.is_null())
    start_time_ = base::Time::Now();

  auto* buffer = AllocateOrReuseBuffer();
  if (!buffer)
    return;

  buffer->DrawFrame();

  for (auto& pair : subscriptions_map_) {
    auto* subscription = pair.first;
    if (!subscription->is_active() || subscription->is_suspended())
      return;

    media::mojom::VideoFrameInfoPtr info = media::mojom::VideoFrameInfo::New();
    info->timestamp = base::Time::Now() - start_time_;
    info->pixel_format = media::PIXEL_FORMAT_ARGB;
    info->coded_size = current_settings_->requested_format.frame_size;
    info->visible_rect = gfx::Rect(info->coded_size);
    info->is_premapped = false;

    subscription->OnFrameReadyInBuffer(
        video_capture::mojom::ReadyFrameInBuffer::New(buffer->buffer_id(),
                                                      /*frame_feedback_id=*/0,
                                                      std::move(info)));
  }
}

FakeCameraDevice::Buffer* FakeCameraDevice::AllocateOrReuseBuffer() {
  DCHECK(current_settings_.has_value());

  const auto buffer_type = current_settings_->buffer_type;
  const auto frame_size = current_settings_->requested_format.frame_size;

  // Can we reuse an existing buffer?
  for (auto& pair : buffer_pool_) {
    Buffer* buffer = pair.second.get();
    if (buffer->CanBeReused(buffer_type, frame_size)) {
      buffer->set_is_in_use(true);
      return buffer;
    }
  }

  if (buffer_pool_.size() >= FakeCameraDevice::kMaxBufferCount) {
    for (auto& pair : subscriptions_map_) {
      auto* subscription = pair.first;
      if (subscription->is_active() && !subscription->is_suspended())
        subscription->OnFrameDropped();
    }
    return nullptr;
  }

  const int buffer_id = g_next_buffer_id++;
  auto unique_buffer = Buffer::Create(buffer_id, buffer_type, frame_size);
  Buffer* buffer_ptr = unique_buffer.get();
  buffer_ptr->set_is_in_use(true);
  buffer_pool_.emplace(buffer_id, std::move(unique_buffer));

  for (auto& pair : subscriptions_map_) {
    auto* subscription = pair.first;
    if (subscription->is_active())
      subscription->OnNewBuffer(buffer_id, buffer_ptr->GetHandle());
  }

  return buffer_ptr;
}

void FakeCameraDevice::RetireAllBuffers() {
  for (auto& pair : buffer_pool_) {
    const int buffer_id = pair.first;
    for (auto& subscription_pair : subscriptions_map_) {
      auto* subscription = subscription_pair.first;
      if (subscription->is_active())
        subscription->OnBufferRetired(buffer_id);
    }
  }
  buffer_pool_.clear();
}

}  // namespace ash