// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Implementation notes: This needs to work on a variety of hardware
// configurations where the speed of the CPU and GPU greatly affect overall
// performance. Spanning several threads, the process of capturing has been
// split up into four conceptual stages:
//
//   1. Reserve Buffer: Before a frame can be captured, a slot in the client's
//      shared-memory IPC buffer is reserved. There are only a few of these;
//      when they run out, it indicates that the downstream client -- likely a
//      video encoder -- is the performance bottleneck, and that the rate of
//      frame capture should be throttled back.
//
//   2. Capture: A bitmap is snapshotted/copied from the RenderWidget's backing
//      store. This is initiated on the UI BrowserThread, and often occurs
//      asynchronously. Where supported, the GPU scales and color converts
//      frames to our desired size, and the readback happens directly into the
//      shared-memory buffer. But this is not always possible, particularly when
//      accelerated compositing is disabled.
//
//   3. Render (if needed): If the web contents cannot be captured directly into
//      our target size and color format, scaling and colorspace conversion must
//      be done on the CPU. A dedicated thread is used for this operation, to
//      avoid blocking the UI thread. The Render stage always reads from a
//      bitmap returned by Capture, and writes into the reserved slot in the
//      shared-memory buffer.
//
//   4. Deliver: The rendered video frame is returned to the client (which
//      implements the VideoCaptureDevice::Client interface). Because all
//      paths have written the frame into the IPC buffer, this step should
//      never need to do an additional copy of the pixel data.
//
// In the best-performing case, the Render step is bypassed: Capture produces
// ready-to-Deliver frames. But when accelerated readback is not possible, the
// system is designed so that Capture and Render may run concurrently. A timing
// diagram helps illustrate this point (@30 FPS):
//
//    Time: 0ms                 33ms                 66ms                 99ms
// thread1: |-Capture-f1------v |-Capture-f2------v  |-Capture-f3----v    |-Capt
// thread2:                   |-Render-f1-----v   |-Render-f2-----v  |-Render-f3
//
// In the above example, both capturing and rendering *each* take almost the
// full 33 ms available between frames, yet we see that the required throughput
// is obtained.
//
// Turning on verbose logging will cause the effective frame rate to be logged
// at 5-second intervals.

#include "content/browser/media/capture/web_contents_video_capture_device.h"

#include <stdint.h>
#include <algorithm>
#include <utility>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/media/capture/cursor_renderer.h"
#include "content/browser/media/capture/web_contents_tracker.h"
#include "content/browser/media/capture/window_activity_tracker.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/render_widget_host_view_frame_subscriber.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_media_capture_id.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_capture_types.h"
#include "media/base/video_frame_metadata.h"
#include "media/base/video_util.h"
#include "media/capture/content/screen_capture_device_core.h"
#include "media/capture/content/thread_safe_capture_oracle.h"
#include "media/capture/content/video_capture_oracle.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/layout.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/size_conversions.h"

#if defined(USE_AURA)
#include "content/browser/media/capture/cursor_renderer_aura.h"
#include "content/browser/media/capture/window_activity_tracker_aura.h"
#endif

namespace content {

namespace {

enum InteractiveModeSettings {
  // Minimum amount of time for which there should be no animation detected
  // to consider interactive mode being active. This is to prevent very brief
  // periods of animated content not being detected (due to CPU fluctations for
  // example) from causing a flip-flop on interactive mode.
  kMinPeriodNoAnimationMillis = 3000
};

void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread,
                          const base::Closure& callback) {
  render_thread.reset();

  // After thread join call the callback on UI thread.
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
}

// Responsible for logging the effective frame rate.
class VideoFrameDeliveryLog {
 public:
  VideoFrameDeliveryLog();

  // Report that the frame posted with |frame_time| has been delivered.
  void ChronicleFrameDelivery(base::TimeTicks frame_time);

 private:
  // The following keep track of and log the effective frame rate whenever
  // verbose logging is turned on.
  base::TimeTicks last_frame_rate_log_time_;
  int count_frames_rendered_;

  DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog);
};

// FrameSubscriber is a proxy to the ThreadSafeCaptureOracle that's compatible
// with RenderWidgetHostViewFrameSubscriber. We create one per event type.
class FrameSubscriber : public RenderWidgetHostViewFrameSubscriber {
 public:
  FrameSubscriber(media::VideoCaptureOracle::Event event_type,
                  const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle,
                  VideoFrameDeliveryLog* delivery_log,
                  base::WeakPtr<content::CursorRenderer> cursor_renderer,
                  base::WeakPtr<content::WindowActivityTracker> tracker)
      : event_type_(event_type),
        oracle_proxy_(oracle),
        delivery_log_(delivery_log),
        cursor_renderer_(cursor_renderer),
        window_activity_tracker_(tracker),
        weak_ptr_factory_(this) {}

  bool ShouldCaptureFrame(
      const gfx::Rect& damage_rect,
      base::TimeTicks present_time,
      scoped_refptr<media::VideoFrame>* storage,
      RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback*
          deliver_frame_cb) override;

  static void DidCaptureFrame(
      base::WeakPtr<FrameSubscriber> frame_subscriber_,
      const media::ThreadSafeCaptureOracle::CaptureFrameCallback&
          capture_frame_cb,
      const scoped_refptr<media::VideoFrame>& frame,
      base::TimeTicks timestamp,
      const gfx::Rect& region_in_frame,
      bool success);

  bool IsUserInteractingWithContent();

 private:
  const media::VideoCaptureOracle::Event event_type_;
  scoped_refptr<media::ThreadSafeCaptureOracle> oracle_proxy_;
  VideoFrameDeliveryLog* const delivery_log_;
  // We need a weak pointer since FrameSubscriber is owned externally and
  // may outlive the cursor renderer.
  base::WeakPtr<CursorRenderer> cursor_renderer_;
  // We need a weak pointer since FrameSubscriber is owned externally and
  // may outlive the ui activity tracker.
  base::WeakPtr<WindowActivityTracker> window_activity_tracker_;
  base::WeakPtrFactory<FrameSubscriber> weak_ptr_factory_;
};

// ContentCaptureSubscription is the relationship between a RenderWidgetHost
// whose content is updating, a subscriber that is deciding which of these
// updates to capture (and where to deliver them to), and a callback that
// knows how to do the capture and prepare the result for delivery.
//
// In practice, this means (a) installing a RenderWidgetHostFrameSubscriber in
// the RenderWidgetHostView, to process compositor updates, and (b) running a
// timer to possibly initiate forced, non-event-driven captures needed by
// downstream consumers that require frame repeats of unchanged content.
//
// All of this happens on the UI thread, although the
// RenderWidgetHostViewFrameSubscriber we install may be dispatching updates
// autonomously on some other thread.
class ContentCaptureSubscription {
 public:
  typedef base::Callback<
      void(const base::TimeTicks&,
           const scoped_refptr<media::VideoFrame>&,
           const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&)>
      CaptureCallback;

  // Create a subscription. Whenever a manual capture is required, the
  // subscription will invoke |capture_callback| on the UI thread to do the
  // work.
  ContentCaptureSubscription(
      const RenderWidgetHost& source,
      const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
      const CaptureCallback& capture_callback);
  ~ContentCaptureSubscription();

 private:
  void OnTimer();

  // Maintain a weak reference to the RenderWidgetHost (via its routing ID),
  // since the instance could be destroyed externally during the lifetime of
  // |this|.
  const int render_process_id_;
  const int render_widget_id_;

  VideoFrameDeliveryLog delivery_log_;
  scoped_ptr<FrameSubscriber> timer_subscriber_;
  CaptureCallback capture_callback_;
  base::Timer timer_;

  // Responsible for tracking the cursor state and input events to make
  // decisions and then render the mouse cursor on the video frame after
  // capture is completed.
  scoped_ptr<content::CursorRenderer> cursor_renderer_;

  // Responsible for tracking the UI events and making a decision on whether
  // user is actively interacting with content.
  scoped_ptr<content::WindowActivityTracker> window_activity_tracker_;

  DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription);
};

// Render the SkBitmap |input| into the given VideoFrame buffer |output|, then
// invoke |done_cb| to indicate success or failure. |input| is expected to be
// ARGB. |output| must be YV12 or I420. Colorspace conversion is always done.
// Scaling and letterboxing will be done as needed.
//
// This software implementation should be used only when GPU acceleration of
// these activities is not possible. This operation may be expensive (tens to
// hundreds of milliseconds), so the caller should ensure that it runs on a
// thread where such a pause would cause UI jank.
void RenderVideoFrame(
    const SkBitmap& input,
    const scoped_refptr<media::VideoFrame>& output,
    const base::Callback<void(const gfx::Rect&, bool)>& done_cb);

// Renews capture subscriptions based on feedback from WebContentsTracker, and
// also executes copying of the backing store on the UI BrowserThread.
class WebContentsCaptureMachine : public media::VideoCaptureMachine {
 public:
  WebContentsCaptureMachine(int render_process_id,
                            int main_render_frame_id,
                            bool enable_auto_throttling);
  ~WebContentsCaptureMachine() override;

  // VideoCaptureMachine overrides.
  void Start(const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
             const media::VideoCaptureParams& params,
             const base::Callback<void(bool)> callback) override;
  void Stop(const base::Closure& callback) override;
  bool IsAutoThrottlingEnabled() const override {
    return auto_throttling_enabled_;
  }

  // Starts a copy from the backing store or the composited surface. Must be run
  // on the UI BrowserThread. |deliver_frame_cb| will be run when the operation
  // completes. The copy will occur to |target|.
  //
  // This may be used as a ContentCaptureSubscription::CaptureCallback.
  void Capture(const base::TimeTicks& start_time,
               const scoped_refptr<media::VideoFrame>& target,
               const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
                   deliver_frame_cb);

 private:
  bool InternalStart(
      const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
      const media::VideoCaptureParams& params);
  void InternalStop(const base::Closure& callback);
  bool IsStarted() const;

  // Computes the preferred size of the target RenderWidget for optimal capture.
  gfx::Size ComputeOptimalViewSize() const;

  // Response callback for RenderWidgetHost::CopyFromBackingStore().
  void DidCopyFromBackingStore(
      const base::TimeTicks& start_time,
      const scoped_refptr<media::VideoFrame>& target,
      const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
          deliver_frame_cb,
      const SkBitmap& bitmap,
      ReadbackResponse response);

  // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame().
  void DidCopyFromCompositingSurfaceToVideoFrame(
      const base::TimeTicks& start_time,
      const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
          deliver_frame_cb,
      const gfx::Rect& region_in_frame,
      bool success);

  // Remove the old subscription, and attempt to start a new one if |had_target|
  // is true.
  void RenewFrameSubscription(bool had_target);

  // Called whenever the render widget is resized.
  void UpdateCaptureSize();

  // Parameters saved in constructor.
  const int initial_render_process_id_;
  const int initial_main_render_frame_id_;

  // Tracks events and calls back to RenewFrameSubscription() to maintain
  // capture on the correct RenderWidgetHost.
  const scoped_refptr<WebContentsTracker> tracker_;

  // Set to false to prevent the capture size from automatically adjusting in
  // response to end-to-end utilization.  This is enabled via the throttling
  // option in the WebContentsVideoCaptureDevice device ID.
  const bool auto_throttling_enabled_;

  // A dedicated worker thread on which SkBitmap->VideoFrame conversion will
  // occur. Only used when this activity cannot be done on the GPU.
  scoped_ptr<base::Thread> render_thread_;

  // Makes all the decisions about which frames to copy, and how.
  scoped_refptr<media::ThreadSafeCaptureOracle> oracle_proxy_;

  // Video capture parameters that this machine is started with.
  media::VideoCaptureParams capture_params_;

  // Last known RenderView size.
  gfx::Size last_view_size_;

  // Responsible for forwarding events from the active RenderWidgetHost to the
  // oracle, and initiating captures accordingly.
  scoped_ptr<ContentCaptureSubscription> subscription_;

  // Weak pointer factory used to invalidate callbacks.
  // NOTE: Weak pointers must be invalidated before all other member variables.
  base::WeakPtrFactory<WebContentsCaptureMachine> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine);
};

bool FrameSubscriber::ShouldCaptureFrame(
    const gfx::Rect& damage_rect,
    base::TimeTicks present_time,
    scoped_refptr<media::VideoFrame>* storage,
    DeliverFrameCallback* deliver_frame_cb) {
  TRACE_EVENT1("gpu.capture", "FrameSubscriber::ShouldCaptureFrame",
               "instance", this);

  media::ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb;

  bool oracle_decision = oracle_proxy_->ObserveEventAndDecideCapture(
      event_type_, damage_rect, present_time, storage, &capture_frame_cb);

  if (!capture_frame_cb.is_null())
    *deliver_frame_cb =
        base::Bind(&FrameSubscriber::DidCaptureFrame,
                   weak_ptr_factory_.GetWeakPtr(), capture_frame_cb, *storage);
  if (oracle_decision)
    delivery_log_->ChronicleFrameDelivery(present_time);
  return oracle_decision;
}

void FrameSubscriber::DidCaptureFrame(
    base::WeakPtr<FrameSubscriber> frame_subscriber_,
    const media::ThreadSafeCaptureOracle::CaptureFrameCallback&
        capture_frame_cb,
    const scoped_refptr<media::VideoFrame>& frame,
    base::TimeTicks timestamp,
    const gfx::Rect& region_in_frame,
    bool success) {
  // We can get a callback in the shutdown sequence for the browser main loop
  // and this can result in a DCHECK failure. Avoid this by doing DCHECK only
  // on success.
  if (success) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    // TODO(isheriff): Unclear if taking a snapshot of cursor here affects user
    // experience in any particular scenarios. Doing it prior to capture may
    // require evaluating region_in_frame in this file.
    if (frame_subscriber_ && frame_subscriber_->cursor_renderer_) {
      CursorRenderer* cursor_renderer =
          frame_subscriber_->cursor_renderer_.get();
      if (cursor_renderer->SnapshotCursorState(region_in_frame))
        cursor_renderer->RenderOnVideoFrame(frame);
      frame->metadata()->SetBoolean(
          media::VideoFrameMetadata::INTERACTIVE_CONTENT,
          frame_subscriber_->IsUserInteractingWithContent());
    }
  }
  capture_frame_cb.Run(frame, timestamp, success);
}

bool FrameSubscriber::IsUserInteractingWithContent() {
  bool interactive_mode = false;
  bool ui_activity = false;
  if (window_activity_tracker_.get()) {
    ui_activity = window_activity_tracker_->IsUiInteractionActive();
  }
  if (cursor_renderer_.get()) {
    bool animation_active =
        (base::TimeTicks::Now() -
         oracle_proxy_->last_time_animation_was_detected()) <
        base::TimeDelta::FromMilliseconds(kMinPeriodNoAnimationMillis);
    if (ui_activity && !animation_active) {
      interactive_mode = true;
    } else if (animation_active) {
      window_activity_tracker_->Reset();
    }
  }
  return interactive_mode;
}

ContentCaptureSubscription::ContentCaptureSubscription(
    const RenderWidgetHost& source,
    const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
    const CaptureCallback& capture_callback)
    : render_process_id_(source.GetProcess()->GetID()),
      render_widget_id_(source.GetRoutingID()),
      delivery_log_(),
      capture_callback_(capture_callback),
      timer_(true, true) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  RenderWidgetHostView* const view = source.GetView();
// TODO(isheriff): Cursor resources currently only available on linux. Remove
// this once we add the necessary resources for windows.
// https://crbug.com/554280 https://crbug.com/549182
#if defined(USE_AURA) && defined(OS_LINUX)
  if (view) {
    cursor_renderer_.reset(
        new content::CursorRendererAura(view->GetNativeView()));
  }
#endif
// TODO(isheriff): Needs implementation on non-aura platforms.
// https://crbug.com/567735
#if defined(USE_AURA)
  if (view) {
    window_activity_tracker_.reset(
        new content::WindowActivityTrackerAura(view->GetNativeView()));
  }
#endif
  timer_subscriber_.reset(new FrameSubscriber(
      media::VideoCaptureOracle::kTimerPoll, oracle_proxy, &delivery_log_,
      cursor_renderer_ ? cursor_renderer_->GetWeakPtr()
                       : base::WeakPtr<CursorRenderer>(),
      window_activity_tracker_ ? window_activity_tracker_->GetWeakPtr()
                               : base::WeakPtr<WindowActivityTracker>()));

  // Subscribe to compositor updates. These will be serviced directly by the
  // oracle.
  if (view) {
    scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber(
        new FrameSubscriber(
            media::VideoCaptureOracle::kCompositorUpdate, oracle_proxy,
            &delivery_log_, cursor_renderer_ ? cursor_renderer_->GetWeakPtr()
                                             : base::WeakPtr<CursorRenderer>(),
            window_activity_tracker_ ? window_activity_tracker_->GetWeakPtr()
                                     : base::WeakPtr<WindowActivityTracker>()));
    view->BeginFrameSubscription(std::move(subscriber));
  }

  // Subscribe to timer events. This instance will service these as well.
  timer_.Start(FROM_HERE,
               std::max(oracle_proxy->min_capture_period(),
                        base::TimeDelta::FromMilliseconds(media
                            ::VideoCaptureOracle::kMinTimerPollPeriodMillis)),
               base::Bind(&ContentCaptureSubscription::OnTimer,
                          base::Unretained(this)));
}

ContentCaptureSubscription::~ContentCaptureSubscription() {
  // If the BrowserThreads have been torn down, then the browser is in the final
  // stages of exiting and it is dangerous to take any further action.  We must
  // return early.  http://crbug.com/396413
  if (!BrowserThread::IsMessageLoopValid(BrowserThread::UI))
    return;

  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  RenderWidgetHost* const source =
      RenderWidgetHost::FromID(render_process_id_, render_widget_id_);
  RenderWidgetHostView* const view = source ? source->GetView() : NULL;
  if (view)
    view->EndFrameSubscription();
}

void ContentCaptureSubscription::OnTimer() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  TRACE_EVENT0("gpu.capture", "ContentCaptureSubscription::OnTimer");

  scoped_refptr<media::VideoFrame> frame;
  RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb;

  const base::TimeTicks start_time = base::TimeTicks::Now();
  if (timer_subscriber_->ShouldCaptureFrame(gfx::Rect(), start_time, &frame,
                                            &deliver_frame_cb)) {
    capture_callback_.Run(start_time, frame, deliver_frame_cb);
  }
}

void RenderVideoFrame(
    const SkBitmap& input,
    const scoped_refptr<media::VideoFrame>& output,
    const base::Callback<void(const gfx::Rect&, bool)>& done_cb) {
  base::ScopedClosureRunner failure_handler(
      base::Bind(done_cb, gfx::Rect(), false));

  SkAutoLockPixels locker(input);

  // Sanity-check the captured bitmap.
  if (input.empty() ||
      !input.readyToDraw() ||
      input.colorType() != kN32_SkColorType ||
      input.width() < 2 || input.height() < 2) {
    DVLOG(1) << "input unacceptable (size="
             << input.getSize()
             << ", ready=" << input.readyToDraw()
             << ", colorType=" << input.colorType() << ')';
    return;
  }

  // Sanity-check the output buffer.
  if (output->format() != media::PIXEL_FORMAT_I420) {
    NOTREACHED();
    return;
  }

  // Calculate the width and height of the content region in the |output|, based
  // on the aspect ratio of |input|.
  const gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
      output->visible_rect(), gfx::Size(input.width(), input.height()));

  // Scale the bitmap to the required size, if necessary.
  SkBitmap scaled_bitmap;
  if (input.width() != region_in_frame.width() ||
      input.height() != region_in_frame.height()) {
    skia::ImageOperations::ResizeMethod method;
    if (input.width() < region_in_frame.width() ||
        input.height() < region_in_frame.height()) {
      // Avoid box filtering when magnifying, because it's actually
      // nearest-neighbor.
      method = skia::ImageOperations::RESIZE_HAMMING1;
    } else {
      method = skia::ImageOperations::RESIZE_BOX;
    }

    TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture",
                                 "Capture", output.get(), "Scale");
    scaled_bitmap = skia::ImageOperations::Resize(input, method,
                                                  region_in_frame.width(),
                                                  region_in_frame.height());
  } else {
    scaled_bitmap = input;
  }

  TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", "Capture", output.get(), "YUV");
  {
    // Align to 2x2 pixel boundaries, as required by
    // media::CopyRGBToVideoFrame().
    const gfx::Rect region_in_yv12_frame(region_in_frame.x() & ~1,
                                         region_in_frame.y() & ~1,
                                         region_in_frame.width() & ~1,
                                         region_in_frame.height() & ~1);
    if (region_in_yv12_frame.IsEmpty())
      return;

    SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap);
    media::CopyRGBToVideoFrame(
        reinterpret_cast<uint8_t*>(scaled_bitmap.getPixels()),
        scaled_bitmap.rowBytes(), region_in_yv12_frame, output.get());
  }

  // The result is now ready.
  ignore_result(failure_handler.Release());
  done_cb.Run(region_in_frame, true);
}

VideoFrameDeliveryLog::VideoFrameDeliveryLog()
    : last_frame_rate_log_time_(),
      count_frames_rendered_(0) {
}

void VideoFrameDeliveryLog::ChronicleFrameDelivery(base::TimeTicks frame_time) {
  // Log frame rate, if verbose logging is turned on.
  static const base::TimeDelta kFrameRateLogInterval =
      base::TimeDelta::FromSeconds(10);
  if (last_frame_rate_log_time_.is_null()) {
    last_frame_rate_log_time_ = frame_time;
    count_frames_rendered_ = 0;
  } else {
    ++count_frames_rendered_;
    const base::TimeDelta elapsed = frame_time - last_frame_rate_log_time_;
    if (elapsed >= kFrameRateLogInterval) {
      const double measured_fps =
          count_frames_rendered_ / elapsed.InSecondsF();
      UMA_HISTOGRAM_COUNTS(
          "TabCapture.FrameRate",
          static_cast<int>(measured_fps));
      VLOG(1) << "Current measured frame rate for "
              << "WebContentsVideoCaptureDevice is " << measured_fps << " FPS.";
      last_frame_rate_log_time_ = frame_time;
      count_frames_rendered_ = 0;
    }
  }
}

WebContentsCaptureMachine::WebContentsCaptureMachine(
    int render_process_id,
    int main_render_frame_id,
    bool enable_auto_throttling)
    : initial_render_process_id_(render_process_id),
      initial_main_render_frame_id_(main_render_frame_id),
      tracker_(new WebContentsTracker(true)),
      auto_throttling_enabled_(enable_auto_throttling),
      weak_ptr_factory_(this) {
  DVLOG(1) << "Created WebContentsCaptureMachine for "
           << render_process_id << ':' << main_render_frame_id
           << (auto_throttling_enabled_ ? " with auto-throttling enabled" : "");
}

WebContentsCaptureMachine::~WebContentsCaptureMachine() {}

bool WebContentsCaptureMachine::IsStarted() const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  return weak_ptr_factory_.HasWeakPtrs();
}

void WebContentsCaptureMachine::Start(
    const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
    const media::VideoCaptureParams& params,
    const base::Callback<void(bool)> callback) {
  // Starts the capture machine asynchronously.
  BrowserThread::PostTaskAndReplyWithResult(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&WebContentsCaptureMachine::InternalStart,
                 base::Unretained(this),
                 oracle_proxy,
                 params),
      callback);
}

bool WebContentsCaptureMachine::InternalStart(
    const scoped_refptr<media::ThreadSafeCaptureOracle>& oracle_proxy,
    const media::VideoCaptureParams& params) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!IsStarted());

  DCHECK(oracle_proxy.get());
  oracle_proxy_ = oracle_proxy;
  capture_params_ = params;

  render_thread_.reset(new base::Thread("WebContentsVideo_RenderThread"));
  if (!render_thread_->Start()) {
    DVLOG(1) << "Failed to spawn render thread.";
    render_thread_.reset();
    return false;
  }

  // Note: Creation of the first WeakPtr in the following statement will cause
  // IsStarted() to return true from now on.
  tracker_->SetResizeChangeCallback(
      base::Bind(&WebContentsCaptureMachine::UpdateCaptureSize,
                 weak_ptr_factory_.GetWeakPtr()));
  tracker_->Start(initial_render_process_id_, initial_main_render_frame_id_,
                  base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription,
                             weak_ptr_factory_.GetWeakPtr()));

  return true;
}

void WebContentsCaptureMachine::Stop(const base::Closure& callback) {
  // Stops the capture machine asynchronously.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE, base::Bind(
          &WebContentsCaptureMachine::InternalStop,
          base::Unretained(this),
          callback));
}

void WebContentsCaptureMachine::InternalStop(const base::Closure& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!IsStarted()) {
    callback.Run();
    return;
  }

  // The following cancels any outstanding callbacks and causes IsStarted() to
  // return false from here onward.
  weak_ptr_factory_.InvalidateWeakPtrs();

  // Note: RenewFrameSubscription() must be called before stopping |tracker_| so
  // the web_contents() can be notified that the capturing is ending.
  RenewFrameSubscription(false);
  tracker_->Stop();

  // The render thread cannot be stopped on the UI thread, so post a message
  // to the thread pool used for blocking operations.
  if (render_thread_.get()) {
    BrowserThread::PostBlockingPoolTask(
        FROM_HERE,
        base::Bind(&DeleteOnWorkerThread, base::Passed(&render_thread_),
                   callback));
  }
}

void WebContentsCaptureMachine::Capture(
    const base::TimeTicks& start_time,
    const scoped_refptr<media::VideoFrame>& target,
    const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
        deliver_frame_cb) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  RenderWidgetHost* rwh = tracker_->GetTargetRenderWidgetHost();
  RenderWidgetHostViewBase* view =
      rwh ? static_cast<RenderWidgetHostViewBase*>(rwh->GetView()) : NULL;
  if (!view) {
    deliver_frame_cb.Run(base::TimeTicks(), gfx::Rect(), false);
    return;
  }

  gfx::Size view_size = view->GetViewBounds().size();
  if (view_size != last_view_size_) {
    last_view_size_ = view_size;

    // Measure the number of kilopixels.
    UMA_HISTOGRAM_COUNTS_10000(
        "TabCapture.ViewChangeKiloPixels",
        view_size.width() * view_size.height() / 1024);
  }

  if (view->CanCopyToVideoFrame()) {
    view->CopyFromCompositingSurfaceToVideoFrame(
        gfx::Rect(view_size),
        target,
        base::Bind(&WebContentsCaptureMachine::
                        DidCopyFromCompositingSurfaceToVideoFrame,
                   weak_ptr_factory_.GetWeakPtr(),
                   start_time, deliver_frame_cb));
  } else {
    const gfx::Size fitted_size = view_size.IsEmpty() ? gfx::Size() :
        media::ComputeLetterboxRegion(target->visible_rect(), view_size).size();
    rwh->CopyFromBackingStore(
        gfx::Rect(),
        fitted_size,  // Size here is a request not always honored.
        base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore,
                   weak_ptr_factory_.GetWeakPtr(),
                   start_time,
                   target,
                   deliver_frame_cb),
        kN32_SkColorType);
  }
}

gfx::Size WebContentsCaptureMachine::ComputeOptimalViewSize() const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // TODO(miu): Propagate capture frame size changes as new "preferred size"
  // updates, rather than just using the max frame size.
  // http://crbug.com/350491
  gfx::Size optimal_size = oracle_proxy_->max_frame_size();

  switch (capture_params_.resolution_change_policy) {
    case media::RESOLUTION_POLICY_FIXED_RESOLUTION:
      break;
    case media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO:
    case media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT: {
      // If the max frame size is close to a common video aspect ratio, compute
      // a standard resolution for that aspect ratio.  For example, given
      // 1365x768, which is very close to 16:9, the optimal size would be
      // 1280x720.  The purpose of this logic is to prevent scaling quality
      // issues caused by "one pixel stretching" and/or odd-to-even dimension
      // scaling, and to improve the performance of consumers of the captured
      // video.
      const auto HasIntendedAspectRatio =
          [](const gfx::Size& size, int width_units, int height_units) {
        const int a = height_units * size.width();
        const int b = width_units * size.height();
        const int percentage_diff = 100 * std::abs((a - b)) / b;
        return percentage_diff <= 1;  // Effectively, anything strictly <2%.
      };
      const auto RoundToExactAspectRatio =
          [](const gfx::Size& size, int width_step, int height_step) {
        const int adjusted_height =
            std::max(size.height() - (size.height() % height_step),
                     height_step);
        DCHECK_EQ((adjusted_height * width_step) % height_step, 0);
        return gfx::Size(adjusted_height * width_step / height_step,
                         adjusted_height);
      };
      if (HasIntendedAspectRatio(optimal_size, 16, 9))
        optimal_size = RoundToExactAspectRatio(optimal_size, 160, 90);
      else if (HasIntendedAspectRatio(optimal_size, 4, 3))
        optimal_size = RoundToExactAspectRatio(optimal_size, 64, 48);
      // Else, do not make an adjustment.
      break;
    }
  }

  // If the ratio between physical and logical pixels is greater than 1:1,
  // shrink |optimal_size| by that amount.  Then, when external code resizes the
  // render widget to the "preferred size," the widget will be physically
  // rendered at the exact capture size, thereby eliminating unnecessary scaling
  // operations in the graphics pipeline.
  RenderWidgetHost* const rwh = tracker_->GetTargetRenderWidgetHost();
  RenderWidgetHostView* const rwhv = rwh ? rwh->GetView() : NULL;
  if (rwhv) {
    const gfx::NativeView view = rwhv->GetNativeView();
    const float scale = ui::GetScaleFactorForNativeView(view);
    if (scale > 1.0f) {
      const gfx::Size shrunk_size =
          gfx::ScaleToFlooredSize(optimal_size, 1.0f / scale);
      if (shrunk_size.width() > 0 && shrunk_size.height() > 0)
        optimal_size = shrunk_size;
    }
  }

  VLOG(1) << "Computed optimal target size: " << optimal_size.ToString();
  return optimal_size;
}

void WebContentsCaptureMachine::DidCopyFromBackingStore(
    const base::TimeTicks& start_time,
    const scoped_refptr<media::VideoFrame>& target,
    const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
        deliver_frame_cb,
    const SkBitmap& bitmap,
    ReadbackResponse response) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  base::TimeTicks now = base::TimeTicks::Now();
  DCHECK(render_thread_.get());
  if (response == READBACK_SUCCESS) {
    UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now - start_time);
    TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", "Capture", target.get(),
                                 "Render");
    render_thread_->task_runner()->PostTask(
        FROM_HERE, media::BindToCurrentLoop(
            base::Bind(&RenderVideoFrame, bitmap, target,
                              base::Bind(deliver_frame_cb, start_time))));
  } else {
    // Capture can fail due to transient issues, so just skip this frame.
    DVLOG(1) << "CopyFromBackingStore failed; skipping frame.";
    deliver_frame_cb.Run(start_time, gfx::Rect(), false);
  }
}

void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
    const base::TimeTicks& start_time,
    const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
        deliver_frame_cb,
    const gfx::Rect& region_in_frame,
    bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  base::TimeTicks now = base::TimeTicks::Now();

  if (success) {
    UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now - start_time);
  } else {
    // Capture can fail due to transient issues, so just skip this frame.
    DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame.";
  }
  deliver_frame_cb.Run(start_time, region_in_frame, success);
}

void WebContentsCaptureMachine::RenewFrameSubscription(bool had_target) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  RenderWidgetHost* const rwh =
      had_target ? tracker_->GetTargetRenderWidgetHost() : nullptr;

  // Always destroy the old subscription before creating a new one.
  const bool had_subscription = !!subscription_;
  subscription_.reset();

  DVLOG(1) << "Renewing frame subscription to RWH@" << rwh
           << ", had_subscription=" << had_subscription;

  if (!rwh) {
    if (had_subscription && tracker_->web_contents())
      tracker_->web_contents()->DecrementCapturerCount();
    if (IsStarted()) {
      // Tracking of WebContents and/or its main frame has failed before Stop()
      // was called, so report this as an error:
      oracle_proxy_->ReportError(FROM_HERE,
                                 "WebContents and/or main frame are gone.");
    }
    return;
  }

  if (!had_subscription && tracker_->web_contents())
    tracker_->web_contents()->IncrementCapturerCount(ComputeOptimalViewSize());

  subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_,
      base::Bind(&WebContentsCaptureMachine::Capture,
                 weak_ptr_factory_.GetWeakPtr())));
}

void WebContentsCaptureMachine::UpdateCaptureSize() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!oracle_proxy_)
    return;
  RenderWidgetHost* const rwh = tracker_->GetTargetRenderWidgetHost();
  RenderWidgetHostView* const view = rwh ? rwh->GetView() : nullptr;
  if (!view)
    return;

  // Convert the view's size from the DIP coordinate space to the pixel
  // coordinate space.  When the view is being rendered on a high-DPI display,
  // this allows the high-resolution image detail to propagate through to the
  // captured video.
  const gfx::Size view_size = view->GetViewBounds().size();
  const gfx::Size physical_size = gfx::ConvertSizeToPixel(
      ui::GetScaleFactorForNativeView(view->GetNativeView()), view_size);
  VLOG(1) << "Computed physical capture size (" << physical_size.ToString()
          << ") from view size (" << view_size.ToString() << ").";

  oracle_proxy_->UpdateCaptureSize(physical_size);
}

}  // namespace

WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
    int render_process_id,
    int main_render_frame_id,
    bool enable_auto_throttling)
    : core_(new media::ScreenCaptureDeviceCore(
          scoped_ptr<media::VideoCaptureMachine>(new WebContentsCaptureMachine(
              render_process_id,
              main_render_frame_id,
              enable_auto_throttling)))) {}

WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() {
  DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying.";
}

// static
media::VideoCaptureDevice* WebContentsVideoCaptureDevice::Create(
    const std::string& device_id) {
  // Parse device_id into render_process_id and main_render_frame_id.
  int render_process_id = -1;
  int main_render_frame_id = -1;
  if (!WebContentsMediaCaptureId::ExtractTabCaptureTarget(
          device_id, &render_process_id, &main_render_frame_id)) {
    return NULL;
  }

  return new WebContentsVideoCaptureDevice(
      render_process_id, main_render_frame_id,
      WebContentsMediaCaptureId::IsAutoThrottlingOptionSet(device_id));
}

void WebContentsVideoCaptureDevice::AllocateAndStart(
    const media::VideoCaptureParams& params,
    scoped_ptr<Client> client) {
  DVLOG(1) << "Allocating " << params.requested_format.frame_size.ToString();
  core_->AllocateAndStart(params, std::move(client));
}

void WebContentsVideoCaptureDevice::StopAndDeAllocate() {
  core_->StopAndDeAllocate();
}

}  // namespace content
