// 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.
#ifndef ASH_CAPTURE_MODE_FAKE_CAMERA_DEVICE_H_
#define ASH_CAPTURE_MODE_FAKE_CAMERA_DEVICE_H_
#include <memory>
#include <optional>
#include "ash/ash_export.h"
#include "base/containers/flat_map.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "media/capture/video/video_capture_device_info.h"
#include "media/capture/video_capture_types.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
#include "services/video_capture/public/mojom/video_source.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace ash {
// A fake implementation of the `VideoSource` mojo API that represents a fake
// camera in ash_unittests.
class ASH_EXPORT FakeCameraDevice
: public video_capture::mojom::VideoSource,
public video_capture::mojom::VideoFrameAccessHandler {
public:
explicit FakeCameraDevice(const media::VideoCaptureDeviceInfo& device_info);
FakeCameraDevice(const FakeCameraDevice&) = delete;
FakeCameraDevice& operator=(const FakeCameraDevice&) = delete;
~FakeCameraDevice() override;
// The max size of the `buffer_pool_` which contains the buffers backing the
// video frames produced by this device.
static constexpr size_t kMaxBufferCount = 4;
// Returns the same bitmap used to paint the produced video frames from this
// camera device for purposes of comparing them with the received frames in
// tests.
static SkBitmap GetProducedFrameAsBitmap(const gfx::Size& frame_size);
const media::VideoCaptureDeviceInfo& device_info() const {
return device_info_;
}
// Binds the given `pending_receiver` to this instance.
void Bind(mojo::PendingReceiver<video_capture::mojom::VideoSource>
pending_receiver);
// Simulates a fatal error on this camera device, by sending an error of type
// `kCrosHalV3DeviceDelegateMojoConnectionError` to the `VideoFrameHandler`.
// See https://crbug.com/1316230 for more details.
void TriggerFatalError();
// video_capture::mojom::VideoSource:
void 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) override;
void RegisterVideoEffectsProcessor(
mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor> remote)
override;
// video_capture::mojom::VideoFrameAccessHandler:
void OnFinishedConsumingBuffer(int32_t buffer_id) override;
private:
class Buffer;
class Subscription;
// Creates a new pending remote whose receiver is bound to this instance. The
// pending remote will be sent to a remote `VideoFrameHandler` so it can
// notify us when done consuming a buffer.
mojo::PendingRemote<video_capture::mojom::VideoFrameAccessHandler>
GetNewAccessHandlerRemote();
// Handles the case when a subscriber `VideoFrameHandler` gets disconnected
// from this instance.
void OnSubscriberDisconnected(Subscription* subscription);
// Called when the subscription activation changes or the settings used to
// open this device changes. If this if for a specific `subscription`, it will
// provided (i.e not null).
void OnSubscriptionActivationChanged(Subscription* subscription);
// Returns true if any of the current subscriptions to this device are active,
// and therefore should be provided with video frames.
bool IsAnySubscriptionActive() const;
// Called repeatedly on every tick of `frame_timer_` which depends on the
// frame desired to open this device.
void OnNextFrame();
// Allocates a new buffer (and therefore notifies active subscribers with
// `OnNewBuffer()`) or reuses an existing one. Either way, the buffer is
// returned. If all buffers in the `buffer_pool_` are used, and there's no
// room for another buffer, active-non-suspended subscribers are notfied with
// `OnFrameDropped()`, and nullptr is returned.
Buffer* AllocateOrReuseBuffer();
// Clears all the buffers in `buffer_pool_` and notifies active subscribers
// with `OnBufferRetired()`.
void RetireAllBuffers();
// The device info this camera device has.
const media::VideoCaptureDeviceInfo device_info_;
mojo::ReceiverSet<video_capture::mojom::VideoSource>
video_source_receiver_set_;
mojo::ReceiverSet<video_capture::mojom::VideoFrameAccessHandler>
access_handler_receiver_set_;
// Maps each owned subscription instance by its instance ptr.
base::flat_map<Subscription*, std::unique_ptr<Subscription>>
subscriptions_map_;
// The current settings used to open this device. It's a nullopt until a
// subscription is created to this device.
std::optional<media::VideoCaptureParams> current_settings_;
// Maps each buffer by its buffer ID.
base::flat_map</*buffer_id=*/int, std::unique_ptr<Buffer>> buffer_pool_;
// The time at which this device started producing video frames.
base::Time start_time_;
// The timer that invokes `OnNextFrame()` repeatedly depending on the frame
// rate requested to open this device.
base::RepeatingTimer frame_timer_;
};
} // namespace ash
#endif // ASH_CAPTURE_MODE_FAKE_CAMERA_DEVICE_H_