// 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.

#include "content/browser/renderer_host/media/video_capture_controller.h"

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <set>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/stl_util.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
#include "content/browser/renderer_host/media/video_capture_device_client.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/common/gpu/client/gl_helper.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "media/base/video_frame.h"

#if !defined(OS_ANDROID)
#include "content/browser/compositor/image_transport_factory.h"
#endif

using media::VideoCaptureFormat;
using media::VideoFrame;
using media::VideoFrameMetadata;

namespace content {

namespace {

static const int kInfiniteRatio = 99999;

#define UMA_HISTOGRAM_ASPECT_RATIO(name, width, height) \
    UMA_HISTOGRAM_SPARSE_SLOWLY( \
        name, \
        (height) ? ((width) * 100) / (height) : kInfiniteRatio);

class SyncTokenClientImpl : public VideoFrame::SyncTokenClient {
 public:
  explicit SyncTokenClientImpl(GLHelper* gl_helper) : gl_helper_(gl_helper) {}
  ~SyncTokenClientImpl() override {}
  void GenerateSyncToken(gpu::SyncToken* sync_token) override {
    gl_helper_->GenerateSyncToken(sync_token);
  }
  void WaitSyncToken(const gpu::SyncToken& sync_token) override {
    gl_helper_->WaitSyncToken(sync_token);
  }

 private:
  GLHelper* gl_helper_;
};

void ReturnVideoFrame(const scoped_refptr<VideoFrame>& video_frame,
                      const gpu::SyncToken& sync_token) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if defined(OS_ANDROID)
  NOTREACHED();
#else
  GLHelper* gl_helper = ImageTransportFactory::GetInstance()->GetGLHelper();
  // UpdateReleaseSyncToken() creates a new sync_token using |gl_helper|, so
  // wait the given |sync_token| using |gl_helper|.
  if (gl_helper) {
    gl_helper->WaitSyncToken(sync_token);
    SyncTokenClientImpl client(gl_helper);
    video_frame->UpdateReleaseSyncToken(&client);
  }
#endif
}

}  // anonymous namespace

struct VideoCaptureController::ControllerClient {
  ControllerClient(VideoCaptureControllerID id,
                   VideoCaptureControllerEventHandler* handler,
                   base::ProcessHandle render_process,
                   media::VideoCaptureSessionId session_id,
                   const media::VideoCaptureParams& params)
      : controller_id(id),
        event_handler(handler),
        render_process_handle(render_process),
        session_id(session_id),
        parameters(params),
        session_closed(false),
        paused(false) {}

  ~ControllerClient() {}

  // ID used for identifying this object.
  const VideoCaptureControllerID controller_id;
  VideoCaptureControllerEventHandler* const event_handler;

  // Handle to the render process that will receive the capture buffers.
  const base::ProcessHandle render_process_handle;
  const media::VideoCaptureSessionId session_id;
  const media::VideoCaptureParams parameters;

  // Buffers that are currently known to this client.
  std::set<int> known_buffers;

  // Buffers currently held by this client, and sync token callback to call when
  // they are returned from the client.
  typedef std::map<int, scoped_refptr<VideoFrame>> ActiveBufferMap;
  ActiveBufferMap active_buffers;

  // State of capture session, controlled by VideoCaptureManager directly. This
  // transitions to true as soon as StopSession() occurs, at which point the
  // client is sent an OnEnded() event. However, because the client retains a
  // VideoCaptureController* pointer, its ControllerClient entry lives on until
  // it unregisters itself via RemoveClient(), which may happen asynchronously.
  //
  // TODO(nick): If we changed the semantics of VideoCaptureHost so that
  // OnEnded() events were processed synchronously (with the RemoveClient() done
  // implicitly), we could avoid tracking this state here in the Controller, and
  // simplify the code in both places.
  bool session_closed;

  // Indicates whether the client is paused, if true, VideoCaptureController
  // stops updating its buffer.
  bool paused;
};

VideoCaptureController::VideoCaptureController(int max_buffers)
    : buffer_pool_(new VideoCaptureBufferPool(max_buffers)),
      state_(VIDEO_CAPTURE_STATE_STARTED),
      has_received_frames_(false),
      weak_ptr_factory_(this) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
}

base::WeakPtr<VideoCaptureController>
VideoCaptureController::GetWeakPtrForIOThread() {
  return weak_ptr_factory_.GetWeakPtr();
}

scoped_ptr<media::VideoCaptureDevice::Client>
VideoCaptureController::NewDeviceClient() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  return make_scoped_ptr(new VideoCaptureDeviceClient(
      this->GetWeakPtrForIOThread(), buffer_pool_));
}

void VideoCaptureController::AddClient(
    VideoCaptureControllerID id,
    VideoCaptureControllerEventHandler* event_handler,
    base::ProcessHandle render_process,
    media::VideoCaptureSessionId session_id,
    const media::VideoCaptureParams& params) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DVLOG(1) << "VideoCaptureController::AddClient() -- id=" << id
           << ", session_id=" << session_id
           << ", params.requested_format="
           << media::VideoCaptureFormat::ToString(params.requested_format);

  // Check that requested VideoCaptureParams are valid and supported.  If not,
  // report an error immediately and punt.
  if (!params.IsValid() ||
      params.requested_format.pixel_format != media::PIXEL_FORMAT_I420 ||
      (params.requested_format.pixel_storage != media::PIXEL_STORAGE_CPU &&
       params.requested_format.pixel_storage !=
           media::PIXEL_STORAGE_GPUMEMORYBUFFER)) {
    // Crash in debug builds since the renderer should not have asked for
    // invalid or unsupported parameters.
    LOG(DFATAL) << "Invalid or unsupported video capture parameters requested: "
                << media::VideoCaptureFormat::ToString(params.requested_format);
    event_handler->OnError(id);
    return;
  }

  // If this is the first client added to the controller, cache the parameters.
  if (!controller_clients_.size())
    video_capture_format_ = params.requested_format;

  // Signal error in case device is already in error state.
  if (state_ == VIDEO_CAPTURE_STATE_ERROR) {
    event_handler->OnError(id);
    return;
  }

  // Do nothing if this client has called AddClient before.
  if (FindClient(id, event_handler, controller_clients_))
    return;

  ControllerClient* client = new ControllerClient(
      id, event_handler, render_process, session_id, params);
  // If we already have gotten frame_info from the device, repeat it to the new
  // client.
  if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
    controller_clients_.push_back(client);
    return;
  }
}

int VideoCaptureController::RemoveClient(
    VideoCaptureControllerID id,
    VideoCaptureControllerEventHandler* event_handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DVLOG(1) << "VideoCaptureController::RemoveClient, id " << id;

  ControllerClient* client = FindClient(id, event_handler, controller_clients_);
  if (!client)
    return kInvalidMediaCaptureSessionId;

  // Take back all buffers held by the |client|.
  for (const auto& buffer : client->active_buffers)
    buffer_pool_->RelinquishConsumerHold(buffer.first, 1);
  client->active_buffers.clear();

  int session_id = client->session_id;
  controller_clients_.remove(client);
  delete client;

  return session_id;
}

void VideoCaptureController::PauseClient(
    VideoCaptureControllerID id,
    VideoCaptureControllerEventHandler* event_handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DVLOG(1) << "VideoCaptureController::PauseClient, id " << id;

  ControllerClient* client = FindClient(id, event_handler, controller_clients_);
  if (!client)
    return;

  DLOG_IF(WARNING, client->paused) << "Redundant client configuration";

  client->paused = true;
}

bool VideoCaptureController::ResumeClient(
    VideoCaptureControllerID id,
    VideoCaptureControllerEventHandler* event_handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DVLOG(1) << "VideoCaptureController::ResumeClient, id " << id;

  ControllerClient* client = FindClient(id, event_handler, controller_clients_);
  if (!client)
    return false;

  if (!client->paused) {
    DVLOG(1) << "Calling resume on unpaused client";
    return false;
  }

  client->paused = false;
  return true;
}

int VideoCaptureController::GetClientCount() const {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  return controller_clients_.size();
}

int VideoCaptureController::GetActiveClientCount() const {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  int active_client_count = 0;
  for (ControllerClient* client : controller_clients_) {
    if (!client->paused)
      ++active_client_count;
  }
  return active_client_count;
}

void VideoCaptureController::StopSession(int session_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id;

  ControllerClient* client = FindClient(session_id, controller_clients_);

  if (client) {
    client->session_closed = true;
    client->event_handler->OnEnded(client->controller_id);
  }
}

void VideoCaptureController::ReturnBuffer(
    VideoCaptureControllerID id,
    VideoCaptureControllerEventHandler* event_handler,
    int buffer_id,
    const gpu::SyncToken& sync_token,
    double consumer_resource_utilization) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  ControllerClient* client = FindClient(id, event_handler, controller_clients_);

  // If this buffer is not held by this client, or this client doesn't exist
  // in controller, do nothing.
  ControllerClient::ActiveBufferMap::iterator iter;
  if (!client || (iter = client->active_buffers.find(buffer_id)) ==
                     client->active_buffers.end()) {
    NOTREACHED();
    return;
  }

  // Set the RESOURCE_UTILIZATION to the maximum of those provided by each
  // consumer (via separate calls to this method that refer to the same
  // VideoFrame).  The producer of this VideoFrame may check this value, after
  // all consumer holds are relinquished, to make quality versus performance
  // trade-off decisions.
  scoped_refptr<VideoFrame> frame = iter->second;
  if (std::isfinite(consumer_resource_utilization) &&
      consumer_resource_utilization >= 0.0) {
    double resource_utilization = -1.0;
    if (frame->metadata()->GetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION,
                                     &resource_utilization)) {
      frame->metadata()->SetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION,
                                   std::max(consumer_resource_utilization,
                                            resource_utilization));
    } else {
      frame->metadata()->SetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION,
                                   consumer_resource_utilization);
    }
  }

  client->active_buffers.erase(iter);
  buffer_pool_->RelinquishConsumerHold(buffer_id, 1);

#if defined(OS_ANDROID)
  DCHECK(!sync_token.HasData());
#endif
  if (sync_token.HasData())
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::Bind(&ReturnVideoFrame, frame, sync_token));
}

const media::VideoCaptureFormat&
VideoCaptureController::GetVideoCaptureFormat() const {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  return video_capture_format_;
}

VideoCaptureController::~VideoCaptureController() {
  STLDeleteContainerPointers(controller_clients_.begin(),
                             controller_clients_.end());
}

void VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread(
    scoped_ptr<media::VideoCaptureDevice::Client::Buffer> buffer,
    const scoped_refptr<VideoFrame>& frame,
    const base::TimeTicks& timestamp) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  const int buffer_id = buffer->id();
  DCHECK_NE(buffer_id, VideoCaptureBufferPool::kInvalidId);

  int count = 0;
  if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
    if (!frame->metadata()->HasKey(VideoFrameMetadata::FRAME_RATE)) {
      frame->metadata()->SetDouble(VideoFrameMetadata::FRAME_RATE,
                                   video_capture_format_.frame_rate);
    }
    scoped_ptr<base::DictionaryValue> metadata(new base::DictionaryValue());
    frame->metadata()->MergeInternalValuesInto(metadata.get());

    // Only I420 pixel format is currently supported.
    DCHECK_EQ(frame->format(), media::PIXEL_FORMAT_I420)
        << "Unsupported pixel format: "
        << media::VideoPixelFormatToString(frame->format());

    // Sanity-checks to confirm |frame| is actually being backed by |buffer|.
    DCHECK(frame->storage_type() == media::VideoFrame::STORAGE_SHMEM ||
           (frame->storage_type() ==
                media::VideoFrame::STORAGE_GPU_MEMORY_BUFFERS));
    DCHECK(frame->data(media::VideoFrame::kYPlane) >= buffer->data(0) &&
           (frame->data(media::VideoFrame::kYPlane) <
            (reinterpret_cast<const uint8_t*>(buffer->data(0)) +
             buffer->mapped_size())))
        << "VideoFrame does not appear to be backed by Buffer";

    for (const auto& client : controller_clients_) {
      if (client->session_closed || client->paused)
        continue;

      // On the first use of a buffer on a client, share the memory handles.
      const bool is_new_buffer = client->known_buffers.insert(buffer_id).second;
      if (is_new_buffer)
        DoNewBufferOnIOThread(client, buffer.get(), frame);

      client->event_handler->OnBufferReady(client->controller_id,
                                           buffer_id,
                                           frame,
                                           timestamp);
      const bool inserted =
          client->active_buffers.insert(std::make_pair(buffer_id, frame))
              .second;
      DCHECK(inserted) << "Unexpected duplicate buffer: " << buffer_id;
      count++;
    }
  }

  if (!has_received_frames_) {
    UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Width",
                         frame->visible_rect().width());
    UMA_HISTOGRAM_COUNTS("Media.VideoCapture.Height",
                         frame->visible_rect().height());
    UMA_HISTOGRAM_ASPECT_RATIO("Media.VideoCapture.AspectRatio",
                               frame->visible_rect().width(),
                               frame->visible_rect().height());
    double frame_rate = 0.0f;
    if (!frame->metadata()->GetDouble(VideoFrameMetadata::FRAME_RATE,
                                      &frame_rate)) {
      frame_rate = video_capture_format_.frame_rate;
    }
    UMA_HISTOGRAM_COUNTS("Media.VideoCapture.FrameRate", frame_rate);
    has_received_frames_ = true;
  }

  buffer_pool_->HoldForConsumers(buffer_id, count);
}

void VideoCaptureController::DoErrorOnIOThread() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  state_ = VIDEO_CAPTURE_STATE_ERROR;

  for (const auto* client : controller_clients_) {
    if (client->session_closed)
       continue;
    client->event_handler->OnError(client->controller_id);
  }
}

void VideoCaptureController::DoLogOnIOThread(const std::string& message) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  MediaStreamManager::SendMessageToNativeLog("Video capture: " + message);
}

void VideoCaptureController::DoBufferDestroyedOnIOThread(
    int buffer_id_to_drop) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  for (auto* client : controller_clients_) {
    if (client->session_closed)
      continue;

    if (client->known_buffers.erase(buffer_id_to_drop)) {
      client->event_handler->OnBufferDestroyed(client->controller_id,
                                               buffer_id_to_drop);
    }
  }
}

void VideoCaptureController::DoNewBufferOnIOThread(
    ControllerClient* client,
    media::VideoCaptureDevice::Client::Buffer* buffer,
    const scoped_refptr<media::VideoFrame>& frame) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  const int buffer_id = buffer->id();

  switch (frame->storage_type()) {
    case media::VideoFrame::STORAGE_GPU_MEMORY_BUFFERS: {
      std::vector<gfx::GpuMemoryBufferHandle> handles;
      const size_t num_planes = media::VideoFrame::NumPlanes(frame->format());
      for (size_t i = 0; i < num_planes; ++i) {
        gfx::GpuMemoryBufferHandle remote_handle;
        buffer_pool_->ShareToProcess2(
            buffer_id, i, client->render_process_handle, &remote_handle);
        handles.push_back(remote_handle);
      }
      client->event_handler->OnBufferCreated2(client->controller_id, handles,
                                              buffer->dimensions(), buffer_id);
      break;
    }
    case media::VideoFrame::STORAGE_SHMEM: {
      base::SharedMemoryHandle remote_handle;
      buffer_pool_->ShareToProcess(buffer_id, client->render_process_handle,
                                   &remote_handle);
      client->event_handler->OnBufferCreated(
          client->controller_id, remote_handle, buffer->mapped_size(),
          buffer_id);
      break;
    }
    default:
      NOTREACHED();
      break;
  }
}

VideoCaptureController::ControllerClient* VideoCaptureController::FindClient(
    VideoCaptureControllerID id,
    VideoCaptureControllerEventHandler* handler,
    const ControllerClients& clients) {
  for (auto* client : clients) {
    if (client->controller_id == id && client->event_handler == handler)
      return client;
  }
  return NULL;
}

VideoCaptureController::ControllerClient* VideoCaptureController::FindClient(
    int session_id,
    const ControllerClients& clients) {
  for (auto client : clients) {
    if (client->session_id == session_id)
      return client;
  }
  return NULL;
}

}  // namespace content
