// Copyright 2013 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 "media/gpu/ipc/service/gpu_video_encode_accelerator.h"

#include <memory>

#include "base/callback.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/shared_memory.h"
#include "base/numerics/safe_math.h"
#include "base/sys_info.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "gpu/ipc/service/gpu_channel.h"
#include "gpu/ipc/service/gpu_channel_manager.h"
#include "ipc/ipc_message_macros.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/limits.h"
#include "media/base/video_frame.h"
#include "media/gpu/gpu_video_accelerator_util.h"
#include "media/gpu/gpu_video_encode_accelerator_factory.h"
#include "media/gpu/ipc/common/media_messages.h"
#include "media/media_features.h"


namespace media {

namespace {

void DropSharedMemory(std::unique_ptr<base::SharedMemory> shm) {
  // Just let |shm| fall out of scope.
}

}  // anonymous namespace

class GpuVideoEncodeAccelerator::MessageFilter : public IPC::MessageFilter {
 public:
  MessageFilter(GpuVideoEncodeAccelerator* owner, int32_t host_route_id)
      : owner_(owner), host_route_id_(host_route_id) {}

  void OnChannelError() override { sender_ = nullptr; }

  void OnChannelClosing() override { sender_ = nullptr; }

  void OnFilterAdded(IPC::Channel* channel) override { sender_ = channel; }

  void OnFilterRemoved() override { owner_->OnFilterRemoved(); }

  bool OnMessageReceived(const IPC::Message& msg) override {
    if (msg.routing_id() != host_route_id_)
      return false;

    IPC_BEGIN_MESSAGE_MAP(MessageFilter, msg)
      IPC_MESSAGE_FORWARD(AcceleratedVideoEncoderMsg_Encode, owner_,
                          GpuVideoEncodeAccelerator::OnEncode)
      IPC_MESSAGE_FORWARD(AcceleratedVideoEncoderMsg_UseOutputBitstreamBuffer,
                          owner_,
                          GpuVideoEncodeAccelerator::OnUseOutputBitstreamBuffer)
      IPC_MESSAGE_FORWARD(
          AcceleratedVideoEncoderMsg_RequestEncodingParametersChange, owner_,
          GpuVideoEncodeAccelerator::OnRequestEncodingParametersChange)
      IPC_MESSAGE_UNHANDLED(return false)
    IPC_END_MESSAGE_MAP()
    return true;
  }

  bool SendOnIOThread(IPC::Message* message) {
    if (!sender_ || message->is_sync()) {
      DCHECK(!message->is_sync());
      delete message;
      return false;
    }
    return sender_->Send(message);
  }

 protected:
  ~MessageFilter() override {}

 private:
  GpuVideoEncodeAccelerator* const owner_;
  const int32_t host_route_id_;
  // The sender to which this filter was added.
  IPC::Sender* sender_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(MessageFilter);
};

GpuVideoEncodeAccelerator::GpuVideoEncodeAccelerator(
    int32_t host_route_id,
    gpu::GpuCommandBufferStub* stub,
    const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
    : host_route_id_(host_route_id),
      stub_(stub),
      input_format_(PIXEL_FORMAT_UNKNOWN),
      output_buffer_size_(0),
      filter_removed_(base::WaitableEvent::ResetPolicy::MANUAL,
                      base::WaitableEvent::InitialState::NOT_SIGNALED),
      encoder_worker_thread_("EncoderWorkerThread"),
      main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
      io_task_runner_(io_task_runner),
      encode_task_runner_(main_task_runner_),
      weak_this_factory_for_encoder_worker_(this),
      weak_this_factory_(this) {
  weak_this_for_encoder_worker_ =
      weak_this_factory_for_encoder_worker_.GetWeakPtr();
  weak_this_ = weak_this_factory_.GetWeakPtr();
  stub_->AddDestructionObserver(this);
}

GpuVideoEncodeAccelerator::~GpuVideoEncodeAccelerator() {
  // This class can only be self-deleted from OnWillDestroyStub(), which means
  // the VEA has already been destroyed in there.
  DCHECK(!encoder_);
  DCHECK(!encoder_worker_thread_.IsRunning());
}

bool GpuVideoEncodeAccelerator::Initialize(VideoPixelFormat input_format,
                                           const gfx::Size& input_visible_size,
                                           VideoCodecProfile output_profile,
                                           uint32_t initial_bitrate) {
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  DVLOG(1) << __func__
           << " input_format=" << VideoPixelFormatToString(input_format)
           << ", input_visible_size=" << input_visible_size.ToString()
           << ", output_profile=" << GetProfileName(output_profile)
           << ", initial_bitrate=" << initial_bitrate;
  DCHECK(!encoder_);

  if (!stub_->channel()->AddRoute(host_route_id_, stub_->sequence_id(), this)) {
    DLOG(ERROR) << __func__ << " failed to add route";
    return false;
  }

  if (input_visible_size.width() > limits::kMaxDimension ||
      input_visible_size.height() > limits::kMaxDimension ||
      input_visible_size.GetArea() > limits::kMaxCanvas) {
    DLOG(ERROR) << __func__ << "too large input_visible_size "
                << input_visible_size.ToString();
    return false;
  }

  const gpu::GpuPreferences& gpu_preferences =
      stub_->channel()->gpu_channel_manager()->gpu_preferences();

  encoder_ = GpuVideoEncodeAcceleratorFactory::CreateVEA(
      input_format, input_visible_size, output_profile, initial_bitrate, this,
      gpu_preferences);
  if (!encoder_) {
    DLOG(ERROR) << __func__ << " Could not create VEA";
    return false;
  }

  if (!encoder_worker_thread_.Start()) {
    encoder_.reset();
    DLOG(ERROR) << "Failed spawning encoder worker thread.";
    return false;
  }

  input_format_ = input_format;
  input_visible_size_ = input_visible_size;
  // Attempt to set up performing encoding tasks on IO thread, if supported
  // by the VEA.
  if (encoder_->TryToSetupEncodeOnSeparateThread(weak_this_, io_task_runner_)) {
    filter_ = new MessageFilter(this, host_route_id_);
    stub_->channel()->AddFilter(filter_.get());
    encode_task_runner_ = io_task_runner_;
  }
  encoder_worker_task_runner_ = encoder_worker_thread_.task_runner();
  return true;
}

bool GpuVideoEncodeAccelerator::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(GpuVideoEncodeAccelerator, message)
    IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderMsg_Encode, OnEncode)
    IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderMsg_UseOutputBitstreamBuffer,
                        OnUseOutputBitstreamBuffer)
    IPC_MESSAGE_HANDLER(
        AcceleratedVideoEncoderMsg_RequestEncodingParametersChange,
        OnRequestEncodingParametersChange)
    IPC_MESSAGE_HANDLER(AcceleratedVideoEncoderMsg_Destroy, OnDestroy)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

bool GpuVideoEncodeAccelerator::Send(IPC::Message* message) {
  if (filter_ && io_task_runner_->BelongsToCurrentThread()) {
    return filter_->SendOnIOThread(message);
  }
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  return stub_->channel()->Send(message);
}

void GpuVideoEncodeAccelerator::RequireBitstreamBuffers(
    unsigned int input_count,
    const gfx::Size& input_coded_size,
    size_t output_buffer_size) {
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  if (!Send(new AcceleratedVideoEncoderHostMsg_RequireBitstreamBuffers(
          host_route_id_, input_count, input_coded_size, output_buffer_size))) {
    DLOG(ERROR) << __func__ << " failed.";
    return;
  }
  input_coded_size_ = input_coded_size;
  output_buffer_size_ = output_buffer_size;
}

void GpuVideoEncodeAccelerator::BitstreamBufferReady(
    int32_t bitstream_buffer_id,
    size_t payload_size,
    bool key_frame,
    base::TimeDelta timestamp) {
  DCHECK(CheckIfCalledOnCorrectThread());
  if (!Send(new AcceleratedVideoEncoderHostMsg_BitstreamBufferReady(
          host_route_id_, bitstream_buffer_id, payload_size, key_frame,
          timestamp))) {
    DLOG(ERROR) << __func__ << " failed.";
  }
}

void GpuVideoEncodeAccelerator::NotifyError(
    VideoEncodeAccelerator::Error error) {
  if (!Send(new AcceleratedVideoEncoderHostMsg_NotifyError(host_route_id_,
                                                           error))) {
    DLOG(ERROR) << __func__ << " failed.";
  }
}

void GpuVideoEncodeAccelerator::OnWillDestroyStub() {
  DVLOG(2) << __func__;
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  DCHECK(stub_);

  // The stub is going away, so we have to stop and destroy VEA here before
  // returning. We cannot destroy the VEA before the IO thread message filter is
  // removed however, since we cannot service incoming messages with VEA gone.
  // We cannot simply check for existence of VEA on IO thread though, because
  // we don't want to synchronize the IO thread with the ChildThread.
  // So we have to wait for the RemoveFilter callback here instead and remove
  // the VEA after it arrives and before returning.
  if (filter_) {
    stub_->channel()->RemoveFilter(filter_.get());
    filter_removed_.Wait();
  }

  // We should stop |encoder_worker_thread_| before releasing |encoder_|, see
  // crbug.com/715759.
  if (encoder_worker_thread_.IsRunning()) {
    encoder_worker_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&GpuVideoEncodeAccelerator::DestroyOnEncoderWorker,
                   weak_this_for_encoder_worker_));
    encoder_worker_thread_.Stop();
  }

  stub_->channel()->RemoveRoute(host_route_id_);
  stub_->RemoveDestructionObserver(this);
  encoder_.reset();
  delete this;
}

// static
gpu::VideoEncodeAcceleratorSupportedProfiles
GpuVideoEncodeAccelerator::GetSupportedProfiles(
    const gpu::GpuPreferences& gpu_preferences) {
  return GpuVideoAcceleratorUtil::ConvertMediaToGpuEncodeProfiles(
      GpuVideoEncodeAcceleratorFactory::GetSupportedProfiles(gpu_preferences));
}

void GpuVideoEncodeAccelerator::OnFilterRemoved() {
  DVLOG(2) << __func__;
  DCHECK(io_task_runner_->BelongsToCurrentThread());

  // We're destroying; cancel all callbacks.
  weak_this_factory_.InvalidateWeakPtrs();
  filter_removed_.Signal();
}

void GpuVideoEncodeAccelerator::OnEncode(
    const AcceleratedVideoEncoderMsg_Encode_Params& params) {
  DVLOG(3) << __func__ << " frame_id = " << params.frame_id
           << ", buffer_size=" << params.buffer_size
           << ", force_keyframe=" << params.force_keyframe;
  DCHECK(CheckIfCalledOnCorrectThread());
  DCHECK_EQ(PIXEL_FORMAT_I420, input_format_);

  if (!encoder_)
    return;

  if (params.frame_id < 0) {
    DLOG(ERROR) << __func__ << " invalid frame_id=" << params.frame_id;
    NotifyError(VideoEncodeAccelerator::kPlatformFailureError);
    return;
  }

  encoder_worker_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&GpuVideoEncodeAccelerator::CreateEncodeFrameOnEncoderWorker,
                 weak_this_for_encoder_worker_, params));
}

void GpuVideoEncodeAccelerator::OnUseOutputBitstreamBuffer(
    int32_t buffer_id,
    base::SharedMemoryHandle buffer_handle,
    uint32_t buffer_size) {
  DVLOG(3) << __func__ << " buffer_id=" << buffer_id
           << ", buffer_size=" << buffer_size;
  DCHECK(CheckIfCalledOnCorrectThread());
  if (!encoder_)
    return;
  if (buffer_id < 0) {
    DLOG(ERROR) << __func__ << " invalid buffer_id=" << buffer_id;
    NotifyError(VideoEncodeAccelerator::kPlatformFailureError);
    return;
  }
  if (buffer_size < output_buffer_size_) {
    DLOG(ERROR) << __func__ << " buffer too small for buffer_id=" << buffer_id;
    NotifyError(VideoEncodeAccelerator::kPlatformFailureError);
    return;
  }
  encoder_->UseOutputBitstreamBuffer(
      BitstreamBuffer(buffer_id, buffer_handle, buffer_size));
}

void GpuVideoEncodeAccelerator::OnDestroy() {
  DVLOG(2) << __func__;
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  OnWillDestroyStub();
}

void GpuVideoEncodeAccelerator::OnRequestEncodingParametersChange(
    uint32_t bitrate,
    uint32_t framerate) {
  DVLOG(2) << __func__ << " bitrate=" << bitrate << ", framerate=" << framerate;
  DCHECK(CheckIfCalledOnCorrectThread());
  if (!encoder_)
    return;
  encoder_->RequestEncodingParametersChange(bitrate, framerate);
}

void GpuVideoEncodeAccelerator::CreateEncodeFrameOnEncoderWorker(
    const AcceleratedVideoEncoderMsg_Encode_Params& params) {
  DVLOG(3) << __func__;
  DCHECK(encoder_worker_task_runner_->BelongsToCurrentThread());

  // Wrap into a SharedMemory in the beginning, so that |params.buffer_handle|
  // is cleaned properly in case of an early return.
  std::unique_ptr<base::SharedMemory> shm(
      new base::SharedMemory(params.buffer_handle, true));
  const uint32_t aligned_offset =
      params.buffer_offset % base::SysInfo::VMAllocationGranularity();
  base::CheckedNumeric<off_t> map_offset = params.buffer_offset;
  map_offset -= aligned_offset;
  base::CheckedNumeric<size_t> map_size = params.buffer_size;
  map_size += aligned_offset;

  if (!map_offset.IsValid() || !map_size.IsValid()) {
    DLOG(ERROR) << __func__ << "  invalid map_offset or map_size";
    encode_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&GpuVideoEncodeAccelerator::NotifyError, weak_this_,
                   VideoEncodeAccelerator::kPlatformFailureError));
    return;
  }

  if (!shm->MapAt(map_offset.ValueOrDie(), map_size.ValueOrDie())) {
    DLOG(ERROR) << __func__ << " could not map frame_id=" << params.frame_id;
    encode_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&GpuVideoEncodeAccelerator::NotifyError, weak_this_,
                   VideoEncodeAccelerator::kPlatformFailureError));
    return;
  }

  uint8_t* shm_memory =
      reinterpret_cast<uint8_t*>(shm->memory()) + aligned_offset;
  scoped_refptr<VideoFrame> frame = VideoFrame::WrapExternalSharedMemory(
      input_format_, input_coded_size_, gfx::Rect(input_visible_size_),
      input_visible_size_, shm_memory, params.buffer_size, params.buffer_handle,
      params.buffer_offset, params.timestamp);
  if (!frame) {
    DLOG(ERROR) << __func__ << " could not create a frame";
    encode_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&GpuVideoEncodeAccelerator::NotifyError, weak_this_,
                   VideoEncodeAccelerator::kPlatformFailureError));
    return;
  }

  // We wrap |shm| in a callback and add it as a destruction observer, so it
  // stays alive and mapped until |frame| goes out of scope.
  frame->AddDestructionObserver(
      base::Bind(&DropSharedMemory, base::Passed(&shm)));
  encode_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&GpuVideoEncodeAccelerator::OnEncodeFrameCreated, weak_this_,
                 params.frame_id, params.force_keyframe, frame));
}

void GpuVideoEncodeAccelerator::DestroyOnEncoderWorker() {
  DCHECK(encoder_worker_task_runner_->BelongsToCurrentThread());
  weak_this_factory_for_encoder_worker_.InvalidateWeakPtrs();
}

void GpuVideoEncodeAccelerator::OnEncodeFrameCreated(
    int32_t frame_id,
    bool force_keyframe,
    const scoped_refptr<media::VideoFrame>& frame) {
  DVLOG(3) << __func__;
  DCHECK(CheckIfCalledOnCorrectThread());

  if (filter_removed_.IsSignaled())
    return;

  if (!frame) {
    DLOG(ERROR) << __func__ << " could not create a frame";
    NotifyError(VideoEncodeAccelerator::kPlatformFailureError);
    return;
  }

  frame->AddDestructionObserver(BindToCurrentLoop(
      base::Bind(&GpuVideoEncodeAccelerator::EncodeFrameFinished,
                 weak_this_factory_.GetWeakPtr(), frame_id)));
  encoder_->Encode(frame, force_keyframe);
}

void GpuVideoEncodeAccelerator::EncodeFrameFinished(int32_t frame_id) {
  DCHECK(CheckIfCalledOnCorrectThread());
  if (!Send(new AcceleratedVideoEncoderHostMsg_NotifyInputDone(host_route_id_,
                                                               frame_id))) {
    DLOG(ERROR) << __func__ << " failed.";
  }
}

bool GpuVideoEncodeAccelerator::CheckIfCalledOnCorrectThread() {
  return (filter_ && io_task_runner_->BelongsToCurrentThread()) ||
         (!filter_ && main_task_runner_->BelongsToCurrentThread());
}

}  // namespace media
