// 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 "media/base/video_frame.h"

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

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/format_macros.h"
#include "base/macros.h"
#include "base/memory/aligned_memory.h"
#include "base/strings/stringprintf.h"
#include "gpu/command_buffer/common/mailbox_holder.h"
#include "media/base/yuv_convert.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

using base::MD5DigestToBase16;

// Helper function that initializes a YV12 frame with white and black scan
// lines based on the |white_to_black| parameter.  If 0, then the entire
// frame will be black, if 1 then the entire frame will be white.
void InitializeYV12Frame(VideoFrame* frame, double white_to_black) {
  EXPECT_EQ(PIXEL_FORMAT_YV12, frame->format());
  const int first_black_row =
      static_cast<int>(frame->coded_size().height() * white_to_black);
  uint8_t* y_plane = frame->data(VideoFrame::kYPlane);
  for (int row = 0; row < frame->coded_size().height(); ++row) {
    int color = (row < first_black_row) ? 0xFF : 0x00;
    memset(y_plane, color, frame->stride(VideoFrame::kYPlane));
    y_plane += frame->stride(VideoFrame::kYPlane);
  }
  uint8_t* u_plane = frame->data(VideoFrame::kUPlane);
  uint8_t* v_plane = frame->data(VideoFrame::kVPlane);
  for (int row = 0; row < frame->coded_size().height(); row += 2) {
    memset(u_plane, 0x80, frame->stride(VideoFrame::kUPlane));
    memset(v_plane, 0x80, frame->stride(VideoFrame::kVPlane));
    u_plane += frame->stride(VideoFrame::kUPlane);
    v_plane += frame->stride(VideoFrame::kVPlane);
  }
}

// Given a |yv12_frame| this method converts the YV12 frame to RGBA and
// makes sure that all the pixels of the RBG frame equal |expect_rgb_color|.
void ExpectFrameColor(media::VideoFrame* yv12_frame,
                      uint32_t expect_rgb_color) {
  ASSERT_EQ(PIXEL_FORMAT_YV12, yv12_frame->format());
  ASSERT_EQ(yv12_frame->stride(VideoFrame::kUPlane),
            yv12_frame->stride(VideoFrame::kVPlane));
  ASSERT_EQ(
      yv12_frame->coded_size().width() & (VideoFrame::kFrameSizeAlignment - 1),
      0);
  ASSERT_EQ(
      yv12_frame->coded_size().height() & (VideoFrame::kFrameSizeAlignment - 1),
      0);

  size_t bytes_per_row = yv12_frame->coded_size().width() * 4u;
  uint8_t* rgb_data = reinterpret_cast<uint8_t*>(
      base::AlignedAlloc(bytes_per_row * yv12_frame->coded_size().height() +
                             VideoFrame::kFrameSizePadding,
                         VideoFrame::kFrameAddressAlignment));

  media::ConvertYUVToRGB32(yv12_frame->data(VideoFrame::kYPlane),
                           yv12_frame->data(VideoFrame::kUPlane),
                           yv12_frame->data(VideoFrame::kVPlane),
                           rgb_data,
                           yv12_frame->coded_size().width(),
                           yv12_frame->coded_size().height(),
                           yv12_frame->stride(VideoFrame::kYPlane),
                           yv12_frame->stride(VideoFrame::kUPlane),
                           bytes_per_row,
                           media::YV12);

  for (int row = 0; row < yv12_frame->coded_size().height(); ++row) {
    uint32_t* rgb_row_data =
        reinterpret_cast<uint32_t*>(rgb_data + (bytes_per_row * row));
    for (int col = 0; col < yv12_frame->coded_size().width(); ++col) {
      SCOPED_TRACE(base::StringPrintf("Checking (%d, %d)", row, col));
      EXPECT_EQ(expect_rgb_color, rgb_row_data[col]);
    }
  }

  base::AlignedFree(rgb_data);
}

// Fill each plane to its reported extents and verify accessors report non
// zero values.  Additionally, for the first plane verify the rows and
// row_bytes values are correct.
void ExpectFrameExtents(VideoPixelFormat format, const char* expected_hash) {
  const unsigned char kFillByte = 0x80;
  const int kWidth = 61;
  const int kHeight = 31;
  const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(1337);

  gfx::Size size(kWidth, kHeight);
  scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
      format, size, gfx::Rect(size), size, kTimestamp);
  ASSERT_TRUE(frame.get());

  int planes = VideoFrame::NumPlanes(format);
  for (int plane = 0; plane < planes; plane++) {
    SCOPED_TRACE(base::StringPrintf("Checking plane %d", plane));
    EXPECT_TRUE(frame->data(plane));
    EXPECT_TRUE(frame->stride(plane));
    EXPECT_TRUE(frame->rows(plane));
    EXPECT_TRUE(frame->row_bytes(plane));

    memset(frame->data(plane), kFillByte,
           frame->stride(plane) * frame->rows(plane));
  }

  base::MD5Context context;
  base::MD5Init(&context);
  VideoFrame::HashFrameForTesting(&context, frame);
  base::MD5Digest digest;
  base::MD5Final(&digest, &context);
  EXPECT_EQ(MD5DigestToBase16(digest), expected_hash);
}

TEST(VideoFrame, CreateFrame) {
  const int kWidth = 64;
  const int kHeight = 48;
  const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(1337);

  // Create a YV12 Video Frame.
  gfx::Size size(kWidth, kHeight);
  scoped_refptr<media::VideoFrame> frame = VideoFrame::CreateFrame(
      media::PIXEL_FORMAT_YV12, size, gfx::Rect(size), size, kTimestamp);
  ASSERT_TRUE(frame.get());

  // Test VideoFrame implementation.
  EXPECT_EQ(media::PIXEL_FORMAT_YV12, frame->format());
  {
    SCOPED_TRACE("");
    InitializeYV12Frame(frame.get(), 0.0f);
    ExpectFrameColor(frame.get(), 0xFF000000);
  }
  base::MD5Digest digest;
  base::MD5Context context;
  base::MD5Init(&context);
  VideoFrame::HashFrameForTesting(&context, frame);
  base::MD5Final(&digest, &context);
  EXPECT_EQ(MD5DigestToBase16(digest), "9065c841d9fca49186ef8b4ef547e79b");
  {
    SCOPED_TRACE("");
    InitializeYV12Frame(frame.get(), 1.0f);
    ExpectFrameColor(frame.get(), 0xFFFFFFFF);
  }
  base::MD5Init(&context);
  VideoFrame::HashFrameForTesting(&context, frame);
  base::MD5Final(&digest, &context);
  EXPECT_EQ(MD5DigestToBase16(digest), "911991d51438ad2e1a40ed5f6fc7c796");

  // Test an empty frame.
  frame = VideoFrame::CreateEOSFrame();
  EXPECT_TRUE(
      frame->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM));
}

TEST(VideoFrame, CreateZeroInitializedFrame) {
  const int kWidth = 2;
  const int kHeight = 2;
  const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(1337);

  // Create a YV12 Video Frame.
  gfx::Size size(kWidth, kHeight);
  scoped_refptr<media::VideoFrame> frame =
      VideoFrame::CreateZeroInitializedFrame(media::PIXEL_FORMAT_YV12, size,
                                             gfx::Rect(size), size, kTimestamp);
  ASSERT_TRUE(frame.get());
  EXPECT_TRUE(frame->IsMappable());

  // Verify that frame is initialized with zeros.
  // TODO(emircan): Check all the contents when we know the exact size of the
  // allocated buffer.
  for (size_t i = 0; i < VideoFrame::NumPlanes(frame->format()); ++i)
    EXPECT_EQ(0, frame->data(i)[0]);
}

TEST(VideoFrame, CreateBlackFrame) {
  const int kWidth = 2;
  const int kHeight = 2;
  const uint8_t kExpectedYRow[] = {0, 0};
  const uint8_t kExpectedUVRow[] = {128};

  scoped_refptr<media::VideoFrame> frame =
      VideoFrame::CreateBlackFrame(gfx::Size(kWidth, kHeight));
  ASSERT_TRUE(frame.get());
  EXPECT_TRUE(frame->IsMappable());

  // Test basic properties.
  EXPECT_EQ(0, frame->timestamp().InMicroseconds());
  EXPECT_FALSE(
      frame->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM));

  // Test |frame| properties.
  EXPECT_EQ(PIXEL_FORMAT_YV12, frame->format());
  EXPECT_EQ(kWidth, frame->coded_size().width());
  EXPECT_EQ(kHeight, frame->coded_size().height());

  // Test frames themselves.
  uint8_t* y_plane = frame->data(VideoFrame::kYPlane);
  for (int y = 0; y < frame->coded_size().height(); ++y) {
    EXPECT_EQ(0, memcmp(kExpectedYRow, y_plane, arraysize(kExpectedYRow)));
    y_plane += frame->stride(VideoFrame::kYPlane);
  }

  uint8_t* u_plane = frame->data(VideoFrame::kUPlane);
  uint8_t* v_plane = frame->data(VideoFrame::kVPlane);
  for (int y = 0; y < frame->coded_size().height() / 2; ++y) {
    EXPECT_EQ(0, memcmp(kExpectedUVRow, u_plane, arraysize(kExpectedUVRow)));
    EXPECT_EQ(0, memcmp(kExpectedUVRow, v_plane, arraysize(kExpectedUVRow)));
    u_plane += frame->stride(VideoFrame::kUPlane);
    v_plane += frame->stride(VideoFrame::kVPlane);
  }
}

static void FrameNoLongerNeededCallback(
    const scoped_refptr<media::VideoFrame>& frame,
    bool* triggered) {
  *triggered = true;
}

TEST(VideoFrame, WrapVideoFrame) {
  const int kWidth = 4;
  const int kHeight = 4;
  const base::TimeDelta kFrameDuration = base::TimeDelta::FromMicroseconds(42);

  scoped_refptr<media::VideoFrame> frame;
  bool done_callback_was_run = false;
  {
    scoped_refptr<media::VideoFrame> wrapped_frame =
        VideoFrame::CreateBlackFrame(gfx::Size(kWidth, kHeight));
    ASSERT_TRUE(wrapped_frame.get());

    gfx::Rect visible_rect(1, 1, 1, 1);
    gfx::Size natural_size = visible_rect.size();
    wrapped_frame->metadata()->SetTimeDelta(
        media::VideoFrameMetadata::FRAME_DURATION, kFrameDuration);
    frame = media::VideoFrame::WrapVideoFrame(
        wrapped_frame, wrapped_frame->format(), visible_rect, natural_size);
    frame->AddDestructionObserver(base::Bind(
        &FrameNoLongerNeededCallback, wrapped_frame, &done_callback_was_run));
    EXPECT_EQ(wrapped_frame->coded_size(), frame->coded_size());
    EXPECT_EQ(wrapped_frame->data(media::VideoFrame::kYPlane),
              frame->data(media::VideoFrame::kYPlane));
    EXPECT_NE(wrapped_frame->visible_rect(), frame->visible_rect());
    EXPECT_EQ(visible_rect, frame->visible_rect());
    EXPECT_NE(wrapped_frame->natural_size(), frame->natural_size());
    EXPECT_EQ(natural_size, frame->natural_size());

    // Verify metadata was copied to the wrapped frame.
    base::TimeDelta frame_duration;
    ASSERT_TRUE(frame->metadata()->GetTimeDelta(
        media::VideoFrameMetadata::FRAME_DURATION, &frame_duration));

    EXPECT_EQ(frame_duration, kFrameDuration);

    // Verify the metadata copy was a deep copy.
    wrapped_frame->metadata()->Clear();
    EXPECT_NE(
        wrapped_frame->metadata()->HasKey(
            media::VideoFrameMetadata::FRAME_DURATION),
        frame->metadata()->HasKey(media::VideoFrameMetadata::FRAME_DURATION));
  }

  EXPECT_FALSE(done_callback_was_run);
  frame = NULL;
  EXPECT_TRUE(done_callback_was_run);
}

// Ensure each frame is properly sized and allocated.  Will trigger OOB reads
// and writes as well as incorrect frame hashes otherwise.
TEST(VideoFrame, CheckFrameExtents) {
  // Each call consists of a Format and the expected hash of all
  // planes if filled with kFillByte (defined in ExpectFrameExtents).
  ExpectFrameExtents(PIXEL_FORMAT_YV12, "8e5d54cb23cd0edca111dd35ffb6ff05");
  ExpectFrameExtents(PIXEL_FORMAT_YV16, "cce408a044b212db42a10dfec304b3ef");
}

static void TextureCallback(gpu::SyncToken* called_sync_token,
                            const gpu::SyncToken& release_sync_token) {
  *called_sync_token = release_sync_token;
}

// Verify the gpu::MailboxHolder::ReleaseCallback is called when VideoFrame is
// destroyed with the default release sync point.
TEST(VideoFrame, TextureNoLongerNeededCallbackIsCalled) {
  gpu::SyncToken called_sync_token(gpu::CommandBufferNamespace::GPU_IO, 0,
                                   gpu::CommandBufferId::FromUnsafeValue(1), 1);

  {
    gpu::MailboxHolder holders[media::VideoFrame::kMaxPlanes] = {
        gpu::MailboxHolder(gpu::Mailbox::Generate(), gpu::SyncToken(), 5)};
    scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
        PIXEL_FORMAT_ARGB, holders,
        base::Bind(&TextureCallback, &called_sync_token),
        gfx::Size(10, 10),   // coded_size
        gfx::Rect(10, 10),   // visible_rect
        gfx::Size(10, 10),   // natural_size
        base::TimeDelta());  // timestamp
    EXPECT_EQ(PIXEL_FORMAT_ARGB, frame->format());
    EXPECT_EQ(VideoFrame::STORAGE_OPAQUE, frame->storage_type());
    EXPECT_TRUE(frame->HasTextures());
  }
  // Nobody set a sync point to |frame|, so |frame| set |called_sync_token|
  // cleared to default value.
  EXPECT_FALSE(called_sync_token.HasData());
}

namespace {

class SyncTokenClientImpl : public VideoFrame::SyncTokenClient {
 public:
  explicit SyncTokenClientImpl(const gpu::SyncToken& sync_token)
      : sync_token_(sync_token) {}
  ~SyncTokenClientImpl() override {}
  void GenerateSyncToken(gpu::SyncToken* sync_token) override {
    *sync_token = sync_token_;
  }
  void WaitSyncToken(const gpu::SyncToken& sync_token) override {}

 private:
  gpu::SyncToken sync_token_;
};

}  // namespace

// Verify the gpu::MailboxHolder::ReleaseCallback is called when VideoFrame is
// destroyed with the release sync point, which was updated by clients.
// (i.e. the compositor, webgl).
TEST(VideoFrame,
     TexturesNoLongerNeededCallbackAfterTakingAndReleasingMailboxes) {
  const int kPlanesNum = 3;
  const gpu::CommandBufferNamespace kNamespace =
      gpu::CommandBufferNamespace::GPU_IO;
  const gpu::CommandBufferId kCommandBufferId =
      gpu::CommandBufferId::FromUnsafeValue(0x123);
  gpu::Mailbox mailbox[kPlanesNum];
  for (int i = 0; i < kPlanesNum; ++i) {
    mailbox[i].name[0] = 50 + 1;
  }

  gpu::SyncToken sync_token(kNamespace, 0, kCommandBufferId, 7);
  sync_token.SetVerifyFlush();
  uint32_t target = 9;
  gpu::SyncToken release_sync_token(kNamespace, 0, kCommandBufferId, 111);
  release_sync_token.SetVerifyFlush();

  gpu::SyncToken called_sync_token;
  {
    gpu::MailboxHolder holders[media::VideoFrame::kMaxPlanes] = {
        gpu::MailboxHolder(mailbox[VideoFrame::kYPlane], sync_token, target),
        gpu::MailboxHolder(mailbox[VideoFrame::kUPlane], sync_token, target),
        gpu::MailboxHolder(mailbox[VideoFrame::kVPlane], sync_token, target),
    };
    scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
        PIXEL_FORMAT_I420, holders,
        base::Bind(&TextureCallback, &called_sync_token),
        gfx::Size(10, 10),   // coded_size
        gfx::Rect(10, 10),   // visible_rect
        gfx::Size(10, 10),   // natural_size
        base::TimeDelta());  // timestamp

    EXPECT_EQ(VideoFrame::STORAGE_OPAQUE, frame->storage_type());
    EXPECT_EQ(PIXEL_FORMAT_I420, frame->format());
    EXPECT_EQ(3u, VideoFrame::NumPlanes(frame->format()));
    EXPECT_TRUE(frame->HasTextures());
    for (size_t i = 0; i < VideoFrame::NumPlanes(frame->format()); ++i) {
      const gpu::MailboxHolder& mailbox_holder = frame->mailbox_holder(i);
      EXPECT_EQ(mailbox[i].name[0], mailbox_holder.mailbox.name[0]);
      EXPECT_EQ(target, mailbox_holder.texture_target);
      EXPECT_EQ(sync_token, mailbox_holder.sync_token);
    }

    SyncTokenClientImpl client(release_sync_token);
    frame->UpdateReleaseSyncToken(&client);
    EXPECT_EQ(sync_token,
              frame->mailbox_holder(VideoFrame::kYPlane).sync_token);
  }
  EXPECT_EQ(release_sync_token, called_sync_token);
}

TEST(VideoFrame, IsValidConfig_OddCodedSize) {
  // Odd sizes are valid for all formats. Odd formats may be internally rounded
  // in VideoFrame::CreateFrame because VideoFrame owns the allocation and can
  // pad the requested coded_size to ensure the UV sample boundaries line up
  // with the Y plane after subsample scaling. See CreateFrame_OddWidth.
  gfx::Size odd_size(677, 288);

  // First choosing a format with sub-sampling for UV.
  EXPECT_TRUE(VideoFrame::IsValidConfig(
      PIXEL_FORMAT_I420, VideoFrame::STORAGE_OWNED_MEMORY, odd_size,
      gfx::Rect(odd_size), odd_size));

  // Next try a format with no sub-sampling for UV.
  EXPECT_TRUE(VideoFrame::IsValidConfig(
      PIXEL_FORMAT_YV24, VideoFrame::STORAGE_OWNED_MEMORY, odd_size,
      gfx::Rect(odd_size), odd_size));
}

TEST(VideoFrame, CreateFrame_OddWidth) {
  // Odd sizes are non-standard for YUV formats that subsample the UV, but they
  // do exist in the wild and should be gracefully handled by VideoFrame in
  // situations where VideoFrame allocates the YUV memory. See discussion in
  // crrev.com/1240833003
  const gfx::Size odd_size(677, 288);
  const base::TimeDelta kTimestamp = base::TimeDelta();

  // First create a frame that sub-samples UV.
  scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
      PIXEL_FORMAT_I420, odd_size, gfx::Rect(odd_size), odd_size, kTimestamp);
  ASSERT_TRUE(frame.get());
  // I420 aligns UV to every 2 Y pixels. Hence, 677 should be rounded to 678
  // which is the nearest value such that width % 2 == 0
  EXPECT_EQ(678, frame->coded_size().width());

  // Next create a frame that does not sub-sample UV.
  frame = VideoFrame::CreateFrame(PIXEL_FORMAT_YV24, odd_size,
                                  gfx::Rect(odd_size), odd_size, kTimestamp);
  ASSERT_TRUE(frame.get());
  // No sub-sampling for YV24 will mean odd width can remain odd since any pixel
  // in the Y plane has a a corresponding pixel in the UV planes at the same
  // index.
  EXPECT_EQ(677, frame->coded_size().width());
}

TEST(VideoFrame, AllocationSize_OddSize) {
  const gfx::Size size(3, 5);
  for (unsigned int i = 1u; i <= PIXEL_FORMAT_MAX; ++i) {
    const VideoPixelFormat format = static_cast<VideoPixelFormat>(i);
    const size_t allocation_size = VideoFrame::AllocationSize(format, size);
    switch (format) {
      case PIXEL_FORMAT_YUV444P9:
      case PIXEL_FORMAT_YUV444P10:
      case PIXEL_FORMAT_YUV444P12:
        EXPECT_EQ(144u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_YUV422P9:
      case PIXEL_FORMAT_YUV422P10:
      case PIXEL_FORMAT_YUV422P12:
        EXPECT_EQ(96u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_YV24:
      case PIXEL_FORMAT_YUV420P9:
      case PIXEL_FORMAT_YUV420P10:
      case PIXEL_FORMAT_YUV420P12:
        EXPECT_EQ(72u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_UYVY:
      case PIXEL_FORMAT_YUY2:
      case PIXEL_FORMAT_YV16:
        EXPECT_EQ(48u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_YV12:
      case PIXEL_FORMAT_I420:
      case PIXEL_FORMAT_NV12:
      case PIXEL_FORMAT_NV21:
      case PIXEL_FORMAT_MT21:
        EXPECT_EQ(36u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_ARGB:
      case PIXEL_FORMAT_XRGB:
      case PIXEL_FORMAT_YV12A:
      case PIXEL_FORMAT_RGB32:
        EXPECT_EQ(60u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_RGB24:
        EXPECT_EQ(45u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_Y16:
        EXPECT_EQ(30u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_Y8:
        EXPECT_EQ(15u, allocation_size) << VideoPixelFormatToString(format);
        break;
      case PIXEL_FORMAT_MJPEG:
      case PIXEL_FORMAT_UNKNOWN:
        break;
    }
  }
}

TEST(VideoFrameMetadata, SetAndThenGetAllKeysForAllTypes) {
  VideoFrameMetadata metadata;

  for (int i = 0; i < VideoFrameMetadata::NUM_KEYS; ++i) {
    const VideoFrameMetadata::Key key = static_cast<VideoFrameMetadata::Key>(i);

    EXPECT_FALSE(metadata.HasKey(key));
    metadata.SetBoolean(key, true);
    EXPECT_TRUE(metadata.HasKey(key));
    bool bool_value = false;
    EXPECT_TRUE(metadata.GetBoolean(key, &bool_value));
    EXPECT_EQ(true, bool_value);
    metadata.Clear();

    EXPECT_FALSE(metadata.HasKey(key));
    metadata.SetInteger(key, i);
    EXPECT_TRUE(metadata.HasKey(key));
    int int_value = -999;
    EXPECT_TRUE(metadata.GetInteger(key, &int_value));
    EXPECT_EQ(i, int_value);
    metadata.Clear();

    EXPECT_FALSE(metadata.HasKey(key));
    metadata.SetDouble(key, 3.14 * i);
    EXPECT_TRUE(metadata.HasKey(key));
    double double_value = -999.99;
    EXPECT_TRUE(metadata.GetDouble(key, &double_value));
    EXPECT_EQ(3.14 * i, double_value);
    metadata.Clear();

    EXPECT_FALSE(metadata.HasKey(key));
    metadata.SetString(key, base::StringPrintf("\xfe%d\xff", i));
    EXPECT_TRUE(metadata.HasKey(key));
    std::string string_value;
    EXPECT_TRUE(metadata.GetString(key, &string_value));
    EXPECT_EQ(base::StringPrintf("\xfe%d\xff", i), string_value);
    metadata.Clear();

    EXPECT_FALSE(metadata.HasKey(key));
    metadata.SetTimeDelta(key, base::TimeDelta::FromInternalValue(42 + i));
    EXPECT_TRUE(metadata.HasKey(key));
    base::TimeDelta delta_value;
    EXPECT_TRUE(metadata.GetTimeDelta(key, &delta_value));
    EXPECT_EQ(base::TimeDelta::FromInternalValue(42 + i), delta_value);
    metadata.Clear();

    EXPECT_FALSE(metadata.HasKey(key));
    metadata.SetTimeTicks(key, base::TimeTicks::FromInternalValue(~(0LL) + i));
    EXPECT_TRUE(metadata.HasKey(key));
    base::TimeTicks ticks_value;
    EXPECT_TRUE(metadata.GetTimeTicks(key, &ticks_value));
    EXPECT_EQ(base::TimeTicks::FromInternalValue(~(0LL) + i), ticks_value);
    metadata.Clear();

    EXPECT_FALSE(metadata.HasKey(key));
    metadata.SetValue(key, base::Value::CreateNullValue());
    EXPECT_TRUE(metadata.HasKey(key));
    const base::Value* const null_value = metadata.GetValue(key);
    EXPECT_TRUE(null_value);
    EXPECT_EQ(base::Value::TYPE_NULL, null_value->GetType());
    metadata.Clear();
  }
}

TEST(VideoFrameMetadata, PassMetadataViaIntermediary) {
  VideoFrameMetadata expected;
  for (int i = 0; i < VideoFrameMetadata::NUM_KEYS; ++i) {
    const VideoFrameMetadata::Key key = static_cast<VideoFrameMetadata::Key>(i);
    expected.SetInteger(key, i);
  }

  VideoFrameMetadata result;
  result.MergeMetadataFrom(&expected);

  for (int i = 0; i < VideoFrameMetadata::NUM_KEYS; ++i) {
    const VideoFrameMetadata::Key key = static_cast<VideoFrameMetadata::Key>(i);
    int value = -1;
    EXPECT_TRUE(result.GetInteger(key, &value));
    EXPECT_EQ(i, value);
  }
}

}  // namespace media
