// Copyright 2014 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/cast/sender/video_encoder.h"

#include <stdint.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "build/build_config.h"
#include "media/base/fake_single_thread_task_runner.h"
#include "media/base/video_frame.h"
#include "media/cast/cast_environment.h"
#include "media/cast/common/rtp_time.h"
#include "media/cast/sender/fake_video_encode_accelerator_factory.h"
#include "media/cast/sender/video_frame_factory.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/video_utility.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(OS_MACOSX)
#include "media/cast/sender/h264_vt_encoder.h"
#endif

namespace media {
namespace cast {

class VideoEncoderTest
    : public ::testing::TestWithParam<std::pair<Codec, bool>> {
 protected:
  VideoEncoderTest()
      : testing_clock_(new base::SimpleTestTickClock()),
        task_runner_(new FakeSingleThreadTaskRunner(testing_clock_)),
        cast_environment_(new CastEnvironment(
            std::unique_ptr<base::TickClock>(testing_clock_),
            task_runner_,
            task_runner_,
            task_runner_)),
        video_config_(GetDefaultVideoSenderConfig()),
        operational_status_(STATUS_UNINITIALIZED),
        count_frames_delivered_(0) {
    testing_clock_->Advance(base::TimeTicks::Now() - base::TimeTicks());
    first_frame_time_ = testing_clock_->NowTicks();
  }

  ~VideoEncoderTest() override {}

  void SetUp() final {
    video_config_.codec = GetParam().first;
    video_config_.use_external_encoder = GetParam().second;

    if (video_config_.use_external_encoder)
      vea_factory_.reset(new FakeVideoEncodeAcceleratorFactory(task_runner_));
  }

  void TearDown() final {
    video_encoder_.reset();
    RunTasksAndAdvanceClock();
  }

  void CreateEncoder() {
    ASSERT_EQ(STATUS_UNINITIALIZED, operational_status_);
    video_config_.video_codec_params.max_number_of_video_buffers_used = 1;
    video_encoder_ = VideoEncoder::Create(
        cast_environment_, video_config_,
        base::Bind(&VideoEncoderTest::OnOperationalStatusChange,
                   base::Unretained(this)),
        base::Bind(
            &FakeVideoEncodeAcceleratorFactory::CreateVideoEncodeAccelerator,
            base::Unretained(vea_factory_.get())),
        base::Bind(&FakeVideoEncodeAcceleratorFactory::CreateSharedMemory,
                   base::Unretained(vea_factory_.get())));
    RunTasksAndAdvanceClock();
    if (is_encoder_present())
      ASSERT_EQ(STATUS_INITIALIZED, operational_status_);
  }

  bool is_encoder_present() const {
    return !!video_encoder_;
  }

  bool is_testing_software_vp8_encoder() const {
    return video_config_.codec == CODEC_VIDEO_VP8 &&
        !video_config_.use_external_encoder;
  }

  bool is_testing_video_toolbox_encoder() const {
    return
#if defined(OS_MACOSX)
        (!video_config_.use_external_encoder &&
         H264VideoToolboxEncoder::IsSupported(video_config_)) ||
#endif
        false;
  }

  bool is_testing_platform_encoder() const {
    return video_config_.use_external_encoder ||
        is_testing_video_toolbox_encoder();
  }

  bool encoder_has_resize_delay() const {
    return is_testing_platform_encoder() && !is_testing_video_toolbox_encoder();
  }

  VideoEncoder* video_encoder() const {
    return video_encoder_.get();
  }

  void DestroyEncoder() {
    video_encoder_.reset();
  }

  base::TimeTicks Now() const {
    return testing_clock_->NowTicks();
  }

  void RunTasksAndAdvanceClock() const {
    DCHECK_GT(video_config_.max_frame_rate, 0);
    const base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds(
        1000000.0 / video_config_.max_frame_rate);
#if defined(OS_MACOSX)
    if (is_testing_video_toolbox_encoder()) {
      // The H264VideoToolboxEncoder (on MAC_OSX and IOS) is not a faked
      // implementation in these tests, and performs its encoding asynchronously
      // on an unknown set of threads.  Therefore, sleep the current thread for
      // the real amount of time to avoid excessively spinning the CPU while
      // waiting for something to happen.
      base::PlatformThread::Sleep(frame_duration);
    }
#endif
    task_runner_->RunTasks();
    testing_clock_->Advance(frame_duration);
  }

  int count_frames_delivered() const {
    return count_frames_delivered_;
  }

  void WaitForAllFramesToBeDelivered(int total_expected) const {
    video_encoder_->EmitFrames();
    while (count_frames_delivered_ < total_expected)
      RunTasksAndAdvanceClock();
  }

  // Creates a new VideoFrame of the given |size|, filled with a test pattern.
  // When available, it attempts to use the VideoFrameFactory provided by the
  // encoder.
  scoped_refptr<media::VideoFrame> CreateTestVideoFrame(const gfx::Size& size) {
    const base::TimeDelta timestamp =
        testing_clock_->NowTicks() - first_frame_time_;
    scoped_refptr<media::VideoFrame> frame;
    if (video_frame_factory_)
      frame = video_frame_factory_->MaybeCreateFrame(size, timestamp);
    if (!frame) {
      frame = media::VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size,
                                             gfx::Rect(size), size, timestamp);
    }
    PopulateVideoFrame(frame.get(), 123);
    return frame;
  }

  // Requests encoding the |video_frame| and has the resulting frame delivered
  // via a callback that checks for expected results.  Returns false if the
  // encoder rejected the request.
  bool EncodeAndCheckDelivery(
      const scoped_refptr<media::VideoFrame>& video_frame,
      FrameId frame_id,
      FrameId reference_frame_id) {
    return video_encoder_->EncodeVideoFrame(
        video_frame, Now(),
        base::Bind(&VideoEncoderTest::DeliverEncodedVideoFrame,
                   base::Unretained(this), frame_id, reference_frame_id,
                   RtpTimeTicks::FromTimeDelta(video_frame->timestamp(),
                                               kVideoFrequency),
                   Now()));
  }

  // If the implementation of |video_encoder_| is ExternalVideoEncoder, check
  // that the VEA factory has responded (by running the callbacks) a specific
  // number of times.  Otherwise, check that the VEA factory is inactive.
  void ExpectVEAResponsesForExternalVideoEncoder(
      int vea_response_count,
      int shm_response_count) const {
    if (!vea_factory_)
      return;
    EXPECT_EQ(vea_response_count, vea_factory_->vea_response_count());
    EXPECT_EQ(shm_response_count, vea_factory_->shm_response_count());
  }

  void SetVEAFactoryAutoRespond(bool auto_respond) {
    if (vea_factory_)
      vea_factory_->SetAutoRespond(auto_respond);
  }

 private:
  void OnOperationalStatusChange(OperationalStatus status) {
    DVLOG(1) << "OnOperationalStatusChange: from " << operational_status_
             << " to " << status;
    operational_status_ = status;

    EXPECT_TRUE(operational_status_ == STATUS_CODEC_REINIT_PENDING ||
                operational_status_ == STATUS_INITIALIZED);

    // Create the VideoFrameFactory the first time status changes to
    // STATUS_INITIALIZED.
    if (operational_status_ == STATUS_INITIALIZED && !video_frame_factory_)
      video_frame_factory_ = video_encoder_->CreateVideoFrameFactory();
  }

  // Checks that |encoded_frame| matches expected values.  This is the method
  // bound in the callback returned from EncodeAndCheckDelivery().
  void DeliverEncodedVideoFrame(
      FrameId expected_frame_id,
      FrameId expected_last_referenced_frame_id,
      RtpTimeTicks expected_rtp_timestamp,
      const base::TimeTicks& expected_reference_time,
      std::unique_ptr<SenderEncodedFrame> encoded_frame) {
    EXPECT_TRUE(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));

    EXPECT_EQ(expected_frame_id, encoded_frame->frame_id);
    EXPECT_EQ(expected_rtp_timestamp, encoded_frame->rtp_timestamp);
    EXPECT_EQ(expected_reference_time, encoded_frame->reference_time);

    // The platform encoders are "black boxes" and may choose to vend key frames
    // and/or empty data at any time.  The software encoders, however, should
    // strictly adhere to expected behavior.
    if (is_testing_platform_encoder()) {
      const bool expected_key_frame =
          expected_frame_id == expected_last_referenced_frame_id;
      const bool have_key_frame =
          encoded_frame->dependency == EncodedFrame::KEY;
      EXPECT_EQ(have_key_frame,
                encoded_frame->frame_id == encoded_frame->referenced_frame_id);
      LOG_IF(WARNING, expected_key_frame != have_key_frame)
          << "Platform encoder chose to emit a "
          << (have_key_frame ? "key" : "delta")
          << " frame instead of the expected kind @ frame_id="
          << encoded_frame->frame_id;
      LOG_IF(WARNING, encoded_frame->data.empty())
          << "Platform encoder returned an empty frame @ frame_id="
          << encoded_frame->frame_id;
    } else {
      if (expected_frame_id != expected_last_referenced_frame_id) {
        EXPECT_EQ(EncodedFrame::DEPENDENT, encoded_frame->dependency);
      } else if (video_config_.video_codec_params
                     .max_number_of_video_buffers_used == 1) {
        EXPECT_EQ(EncodedFrame::KEY, encoded_frame->dependency);
      }
      EXPECT_EQ(expected_last_referenced_frame_id,
                encoded_frame->referenced_frame_id);
      EXPECT_FALSE(encoded_frame->data.empty());
      ASSERT_TRUE(std::isfinite(encoded_frame->encoder_utilization));
      EXPECT_LE(0.0, encoded_frame->encoder_utilization);
      ASSERT_TRUE(std::isfinite(encoded_frame->lossy_utilization));
      EXPECT_LE(0.0, encoded_frame->lossy_utilization);
    }

    ++count_frames_delivered_;
  }

  base::SimpleTestTickClock* const testing_clock_;  // Owned by CastEnvironment.
  const scoped_refptr<FakeSingleThreadTaskRunner> task_runner_;
  const scoped_refptr<CastEnvironment> cast_environment_;
  FrameSenderConfig video_config_;
  std::unique_ptr<FakeVideoEncodeAcceleratorFactory> vea_factory_;
  base::TimeTicks first_frame_time_;
  OperationalStatus operational_status_;
  std::unique_ptr<VideoEncoder> video_encoder_;
  std::unique_ptr<VideoFrameFactory> video_frame_factory_;

  int count_frames_delivered_;

  DISALLOW_COPY_AND_ASSIGN(VideoEncoderTest);
};

// A simple test to encode three frames of video, expecting to see one key frame
// followed by two delta frames.
TEST_P(VideoEncoderTest, GeneratesKeyFrameThenOnlyDeltaFrames) {
  CreateEncoder();
  SetVEAFactoryAutoRespond(true);

  EXPECT_EQ(0, count_frames_delivered());
  ExpectVEAResponsesForExternalVideoEncoder(0, 0);

  FrameId frame_id = FrameId::first();
  FrameId reference_frame_id = FrameId::first();
  const gfx::Size frame_size(1280, 720);

  // Some encoders drop one or more frames initially while the encoder
  // initializes. Then, for all encoders, expect one key frame is delivered.
  bool accepted_first_frame = false;
  do {
    accepted_first_frame = EncodeAndCheckDelivery(
        CreateTestVideoFrame(frame_size), frame_id, reference_frame_id);
    if (!encoder_has_resize_delay())
      EXPECT_TRUE(accepted_first_frame);
    RunTasksAndAdvanceClock();
  } while (!accepted_first_frame);
  ExpectVEAResponsesForExternalVideoEncoder(1, 3);

  // Expect the remaining frames are encoded as delta frames.
  for (++frame_id; frame_id < FrameId::first() + 3;
       ++frame_id, ++reference_frame_id) {
    EXPECT_TRUE(EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size),
                                       frame_id,
                                       reference_frame_id));
    RunTasksAndAdvanceClock();
  }

  WaitForAllFramesToBeDelivered(3);
  ExpectVEAResponsesForExternalVideoEncoder(1, 3);
}

// Tests that the encoder continues to output EncodedFrames as the frame size
// changes.  See media/cast/receiver/video_decoder_unittest.cc for a complete
// encode/decode cycle of varied frame sizes that actually checks the frame
// content.
TEST_P(VideoEncoderTest, EncodesVariedFrameSizes) {
  CreateEncoder();
  SetVEAFactoryAutoRespond(true);

  EXPECT_EQ(0, count_frames_delivered());
  ExpectVEAResponsesForExternalVideoEncoder(0, 0);

  std::vector<gfx::Size> frame_sizes;
  frame_sizes.push_back(gfx::Size(128, 72));
  frame_sizes.push_back(gfx::Size(64, 36));    // Shrink both dimensions.
  frame_sizes.push_back(gfx::Size(30, 20));    // Shrink both dimensions again.
  frame_sizes.push_back(gfx::Size(20, 30));    // Same area.
  frame_sizes.push_back(gfx::Size(60, 40));    // Grow both dimensions.
  frame_sizes.push_back(gfx::Size(58, 40));    // Shrink only one dimension.
  frame_sizes.push_back(gfx::Size(58, 38));    // Shrink the other dimension.
  frame_sizes.push_back(gfx::Size(32, 18));    // Shrink both dimensions again.
  frame_sizes.push_back(gfx::Size(34, 18));    // Grow only one dimension.
  frame_sizes.push_back(gfx::Size(34, 20));    // Grow the other dimension.
  frame_sizes.push_back(gfx::Size(192, 108));  // Grow both dimensions again.

  FrameId frame_id = FrameId::first();

  // Encode one frame at each size. For encoders with a resize delay, except no
  // frames to be delivered since each frame size change will sprun
  // re-initialization of the underlying encoder. Otherwise expect all key
  // frames to come out.
  for (const auto& frame_size : frame_sizes) {
    EXPECT_EQ(!encoder_has_resize_delay(),
              EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size), frame_id,
                                     frame_id));
    RunTasksAndAdvanceClock();
    if (!encoder_has_resize_delay())
      ++frame_id;
  }

  // Encode three frames at each size. For encoders with a resize delay, expect
  // the first one or more frames are dropped while the encoder re-inits. Then,
  // for all encoders, expect one key frame followed by all delta frames.
  for (const auto& frame_size : frame_sizes) {
    bool accepted_first_frame = false;
    do {
      accepted_first_frame = EncodeAndCheckDelivery(
          CreateTestVideoFrame(frame_size), frame_id, frame_id);
      if (!encoder_has_resize_delay())
        EXPECT_TRUE(accepted_first_frame);
      RunTasksAndAdvanceClock();
    } while (!accepted_first_frame);
    ++frame_id;
    for (int i = 1; i < 3; ++i, ++frame_id) {
      EXPECT_TRUE(EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size),
                                         frame_id,
                                         frame_id - 1));
      RunTasksAndAdvanceClock();
    }
  }

  WaitForAllFramesToBeDelivered(3 * frame_sizes.size());
  ExpectVEAResponsesForExternalVideoEncoder(
      2 * frame_sizes.size(), 6 * frame_sizes.size());
}

// Verify that everything goes well even if ExternalVideoEncoder is destroyed
// before it has a chance to receive the VEA creation callback.  For all other
// encoders, this tests that the encoder can be safely destroyed before the task
// is run that delivers the first EncodedFrame.
TEST_P(VideoEncoderTest, CanBeDestroyedBeforeVEAIsCreated) {
  CreateEncoder();

  // Send a frame to spawn creation of the ExternalVideoEncoder instance.
  EncodeAndCheckDelivery(CreateTestVideoFrame(gfx::Size(128, 72)),
                         FrameId::first(), FrameId::first());

  // Destroy the encoder, and confirm the VEA Factory did not respond yet.
  DestroyEncoder();
  ExpectVEAResponsesForExternalVideoEncoder(0, 0);

  // Allow the VEA Factory to respond by running the creation callback.  When
  // the task runs, it will be a no-op since the weak pointers to the
  // ExternalVideoEncoder were invalidated.
  SetVEAFactoryAutoRespond(true);
  RunTasksAndAdvanceClock();
  ExpectVEAResponsesForExternalVideoEncoder(1, 0);
}

namespace {
std::vector<std::pair<Codec, bool>> DetermineEncodersToTest() {
  std::vector<std::pair<Codec, bool>> values;
  // Fake encoder.
  values.push_back(std::make_pair(CODEC_VIDEO_FAKE, false));
  // Software VP8 encoder.
  values.push_back(std::make_pair(CODEC_VIDEO_VP8, false));
  // Hardware-accelerated encoder (faked).
  values.push_back(std::make_pair(CODEC_VIDEO_VP8, true));
#if defined(OS_MACOSX)
  // VideoToolbox encoder (when VideoToolbox is present).
  FrameSenderConfig video_config = GetDefaultVideoSenderConfig();
  video_config.use_external_encoder = false;
  video_config.codec = CODEC_VIDEO_H264;
  if (H264VideoToolboxEncoder::IsSupported(video_config))
    values.push_back(std::make_pair(CODEC_VIDEO_H264, false));
#endif
  return values;
}
}  // namespace

INSTANTIATE_TEST_CASE_P(
    , VideoEncoderTest, ::testing::ValuesIn(DetermineEncodersToTest()));

}  // namespace cast
}  // namespace media
