// Copyright 2012 The Chromium Authors
// 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 <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <algorithm>
#include <array>
#include <atomic>
#include <climits>
#include <cstddef>
#include <memory>
#include <numeric>
#include <string_view>
#include <utility>

#include "base/bits.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/memory.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "media/base/color_plane_layout.h"
#include "media/base/format_utils.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_util.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "third_party/skia/include/core/SkYUVAPixmaps.h"
#include "ui/gfx/geometry/point.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ui/ozone/public/client_native_pixmap_factory_ozone.h"  // nogncheck
#endif

#if BUILDFLAG(IS_APPLE)
#include <CoreVideo/CVPixelBuffer.h>

#include "base/apple/scoped_cftyperef.h"
#include "ui/gfx/mac/io_surface.h"
#endif

namespace media {

namespace {

VideoFrame::ID GetNextID() {
  static std::atomic_uint64_t counter(1u);
  return VideoFrame::ID::FromUnsafeValue(
      counter.fetch_add(1u, std::memory_order_relaxed));
}

// Helper to provide gfx::Rect::Intersect() as an expression.
gfx::Rect Intersection(gfx::Rect a, const gfx::Rect& b) {
  a.Intersect(b);
  return a;
}

}  // namespace

// static
std::string VideoFrame::StorageTypeToString(
    const VideoFrame::StorageType storage_type) {
  switch (storage_type) {
    case VideoFrame::STORAGE_UNKNOWN:
      return "UNKNOWN";
    case VideoFrame::STORAGE_OPAQUE:
      return "OPAQUE";
    case VideoFrame::STORAGE_UNOWNED_MEMORY:
      return "UNOWNED_MEMORY";
    case VideoFrame::STORAGE_OWNED_MEMORY:
      return "OWNED_MEMORY";
    case VideoFrame::STORAGE_SHMEM:
      return "SHMEM";
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
    case VideoFrame::STORAGE_DMABUFS:
      return "DMABUFS";
#endif
    case VideoFrame::STORAGE_MAPPABLE_SHARED_IMAGE:
      return "MAPPABLE_SHARED_IMAGE";
  }

  NOTREACHED() << "Invalid StorageType provided: " << storage_type;
}

// static
bool VideoFrame::StorageTypeAllowsDirectCpuAccess(
    VideoFrame::StorageType storage_type) {
  // CPU memory is the only kind of storage that is mappable at the level of
  // VideoFrame itself (other types of storage such as DMA bufs and
  // MappableSharedImage can be mapped, but not at the level of VideoFrame).
  return storage_type == VideoFrame::STORAGE_UNOWNED_MEMORY ||
         storage_type == VideoFrame::STORAGE_OWNED_MEMORY ||
         storage_type == VideoFrame::STORAGE_SHMEM;
}

// static
bool VideoFrame::IsValidPlane(VideoPixelFormat format, size_t plane) {
  DCHECK_LE(NumPlanes(format), static_cast<size_t>(kMaxPlanes));
  return plane < NumPlanes(format);
}

// static
gfx::Size VideoFrame::SampleSize(VideoPixelFormat format, size_t plane) {
  DCHECK(IsValidPlane(format, plane));

  switch (plane) {
    case Plane::kY:  // and Plane::kARGB:
    case Plane::kA:
      return gfx::Size(1, 1);

    case Plane::kU:  // and Plane::kUV:
    case Plane::kV:  // and Plane::kATriPlanar:
      switch (format) {
        case PIXEL_FORMAT_I444:
        case PIXEL_FORMAT_YUV444P10:
        case PIXEL_FORMAT_YUV444P12:
        case PIXEL_FORMAT_Y16:
        case PIXEL_FORMAT_I444A:
        case PIXEL_FORMAT_YUV444AP10:
        case PIXEL_FORMAT_NV24:
        case PIXEL_FORMAT_P410LE:
          return gfx::Size(1, 1);

        case PIXEL_FORMAT_I422:
        case PIXEL_FORMAT_YUV422P10:
        case PIXEL_FORMAT_YUV422P12:
        case PIXEL_FORMAT_I422A:
        case PIXEL_FORMAT_YUV422AP10:
        case PIXEL_FORMAT_NV16:
        case PIXEL_FORMAT_P210LE:
          return gfx::Size(2, 1);

        case PIXEL_FORMAT_YV12:
        case PIXEL_FORMAT_I420:
        case PIXEL_FORMAT_I420A:
        case PIXEL_FORMAT_NV12:
        case PIXEL_FORMAT_NV21:
        case PIXEL_FORMAT_YUV420P10:
        case PIXEL_FORMAT_YUV420P12:
        case PIXEL_FORMAT_P010LE:
        case PIXEL_FORMAT_YUV420AP10:
          return gfx::Size(2, 2);

        case PIXEL_FORMAT_NV12A:
          return plane == Plane::kUV ? gfx::Size(2, 2) : gfx::Size(1, 1);

        case PIXEL_FORMAT_UYVY:
        case PIXEL_FORMAT_UNKNOWN:
        case PIXEL_FORMAT_YUY2:
        case PIXEL_FORMAT_ARGB:
        case PIXEL_FORMAT_XRGB:
        case PIXEL_FORMAT_RGB24:
        case PIXEL_FORMAT_MJPEG:
        case PIXEL_FORMAT_ABGR:
        case PIXEL_FORMAT_XBGR:
        case PIXEL_FORMAT_XR30:
        case PIXEL_FORMAT_XB30:
        case PIXEL_FORMAT_BGRA:
        case PIXEL_FORMAT_RGBAF16:
          break;
      }
  }
  NOTREACHED();
}

// Checks if |source_format| can be wrapped into a |target_format| frame.
static bool AreValidPixelFormatsForWrap(VideoPixelFormat source_format,
                                        VideoPixelFormat target_format) {
  return source_format == target_format ||
         (source_format == PIXEL_FORMAT_I420A &&
          target_format == PIXEL_FORMAT_I420) ||
         (source_format == PIXEL_FORMAT_I422A &&
          target_format == PIXEL_FORMAT_I422) ||
         (source_format == PIXEL_FORMAT_I444A &&
          target_format == PIXEL_FORMAT_I444) ||
         (source_format == PIXEL_FORMAT_YUV420AP10 &&
          target_format == PIXEL_FORMAT_YUV420P10) ||
         (source_format == PIXEL_FORMAT_YUV422AP10 &&
          target_format == PIXEL_FORMAT_YUV422P10) ||
         (source_format == PIXEL_FORMAT_YUV444AP10 &&
          target_format == PIXEL_FORMAT_YUV444P10) ||
         (source_format == PIXEL_FORMAT_ARGB &&
          target_format == PIXEL_FORMAT_XRGB) ||
         (source_format == PIXEL_FORMAT_ABGR &&
          target_format == PIXEL_FORMAT_XBGR);
}

// Creates VideoFrameLayout for tightly packed frame.
static std::optional<VideoFrameLayout> GetDefaultLayout(
    VideoPixelFormat format,
    const gfx::Size& coded_size) {
  std::vector<ColorPlaneLayout> planes;

  switch (format) {
    case PIXEL_FORMAT_I420:
    case PIXEL_FORMAT_I420A:
    case PIXEL_FORMAT_YUV420P10:
    case PIXEL_FORMAT_YUV420P12:
    case PIXEL_FORMAT_YUV420AP10: {
      int sample_bytes =
          VideoFrame::BytesPerElement(format, VideoFrame::Plane::kY);
      int y_stride = coded_size.width() * sample_bytes;
      int y_size = y_stride * coded_size.height();
      int uv_width = (coded_size.width() + 1) / 2;
      int uv_height = (coded_size.height() + 1) / 2;
      int uv_stride = uv_width * sample_bytes;
      int uv_size = uv_stride * uv_height;
      planes = std::vector<ColorPlaneLayout>{
          ColorPlaneLayout(y_stride, 0, y_size),
          ColorPlaneLayout(uv_stride, y_size, uv_size),
          ColorPlaneLayout(uv_stride, y_size + uv_size, uv_size),
      };
      if (format == PIXEL_FORMAT_I420A || format == PIXEL_FORMAT_YUV420AP10) {
        planes.emplace_back(y_stride, y_size + uv_size * 2, y_size);
      }
      break;
    }

    case PIXEL_FORMAT_I422:
    case PIXEL_FORMAT_I422A:
    case PIXEL_FORMAT_YUV422P10:
    case PIXEL_FORMAT_YUV422P12:
    case PIXEL_FORMAT_YUV422AP10: {
      int sample_bytes =
          VideoFrame::BytesPerElement(format, VideoFrame::Plane::kY);
      int y_stride = coded_size.width() * sample_bytes;
      int y_size = y_stride * coded_size.height();
      int uv_width = (coded_size.width() + 1) / 2;
      int uv_stride = uv_width * sample_bytes;
      int uv_size = uv_stride * coded_size.height();
      planes = std::vector<ColorPlaneLayout>{
          ColorPlaneLayout(y_stride, 0, y_size),
          ColorPlaneLayout(uv_stride, y_size, uv_size),
          ColorPlaneLayout(uv_stride, y_size + uv_size, uv_size),
      };
      if (format == PIXEL_FORMAT_I422A || format == PIXEL_FORMAT_YUV422AP10) {
        planes.emplace_back(y_stride, y_size + uv_size * 2, y_size);
      }
      break;
    }

    case PIXEL_FORMAT_I444:
    case PIXEL_FORMAT_I444A:
    case PIXEL_FORMAT_YUV444P10:
    case PIXEL_FORMAT_YUV444P12:
    case PIXEL_FORMAT_YUV444AP10: {
      int sample_bytes =
          VideoFrame::BytesPerElement(format, VideoFrame::Plane::kY);
      int stride = coded_size.width() * sample_bytes;
      int plane_size = stride * coded_size.height();
      planes = std::vector<ColorPlaneLayout>{
          ColorPlaneLayout(stride, 0, plane_size),
          ColorPlaneLayout(stride, plane_size, plane_size),
          ColorPlaneLayout(stride, plane_size * 2, plane_size),
      };
      if (format == PIXEL_FORMAT_I444A || format == PIXEL_FORMAT_YUV444AP10) {
        planes.emplace_back(stride, plane_size * 3, plane_size);
      }
      break;
    }

    case PIXEL_FORMAT_Y16:
      planes = std::vector<ColorPlaneLayout>{ColorPlaneLayout(
          coded_size.width() * 2, 0, coded_size.GetArea() * 2)};
      break;

    case PIXEL_FORMAT_ARGB:
    case PIXEL_FORMAT_XRGB:
    case PIXEL_FORMAT_ABGR:
    case PIXEL_FORMAT_XBGR:
      planes = std::vector<ColorPlaneLayout>{ColorPlaneLayout(
          coded_size.width() * 4, 0, coded_size.GetArea() * 4)};
      break;

    case PIXEL_FORMAT_NV12:
    case PIXEL_FORMAT_NV12A:
    case PIXEL_FORMAT_NV16:
    case PIXEL_FORMAT_NV24:
    case PIXEL_FORMAT_P010LE:
    case PIXEL_FORMAT_P210LE:
    case PIXEL_FORMAT_P410LE: {
      const bool is_420 = format == PIXEL_FORMAT_NV12 ||
                          format == PIXEL_FORMAT_NV12A ||
                          format == PIXEL_FORMAT_P010LE;
      const bool is_444 =
          format == PIXEL_FORMAT_NV24 || format == PIXEL_FORMAT_P410LE;
      int sample_bytes =
          VideoFrame::BytesPerElement(format, VideoFrame::Plane::kY);
      int y_stride = coded_size.width() * sample_bytes;
      int y_size = y_stride * coded_size.height();
      int uv_width = is_444 ? coded_size.width() : (coded_size.width() + 1) / 2;
      int uv_height =
          is_420 ? (coded_size.height() + 1) / 2 : coded_size.height();
      int uv_stride = uv_width * sample_bytes * 2;
      int uv_size = uv_stride * uv_height;
      planes = std::vector<ColorPlaneLayout>{
          ColorPlaneLayout(y_stride, 0, y_size),
          ColorPlaneLayout(uv_stride, y_size, uv_size),
      };
      if (format == PIXEL_FORMAT_NV12A) {
        planes.emplace_back(y_stride, y_size + uv_size, y_size);
      }
      break;
    }

    default:
      DLOG(ERROR) << "Unsupported pixel format"
                  << VideoPixelFormatToString(format);
      return std::nullopt;
  }

  return VideoFrameLayout::CreateWithPlanes(format, coded_size, planes);
}

// static
bool VideoFrame::IsValidConfig(VideoPixelFormat format,
                               StorageType storage_type,
                               const gfx::Size& coded_size,
                               const gfx::Rect& visible_rect,
                               const gfx::Size& natural_size) {
  return IsValidConfigInternal(format, FrameControlType::kNone, coded_size,
                               visible_rect, natural_size);
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateFrame(VideoPixelFormat format,
                                                  const gfx::Size& coded_size,
                                                  const gfx::Rect& visible_rect,
                                                  const gfx::Size& natural_size,
                                                  base::TimeDelta timestamp) {
  return CreateFrameInternal(format, coded_size, visible_rect, natural_size,
                             timestamp, false);
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateVideoHoleFrame(
    const base::UnguessableToken& overlay_plane_id,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp) {
  return VideoFrame::WrapTrackingToken(PIXEL_FORMAT_UNKNOWN, overlay_plane_id,
                                       /*coded_size=*/natural_size,
                                       /*visible_rect=*/gfx::Rect(natural_size),
                                       natural_size, timestamp);
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapTrackingToken(
    VideoPixelFormat format,
    const base::UnguessableToken& tracking_token,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp) {
  auto layout = VideoFrameLayout::Create(format, coded_size);
  if (!layout) {
    DLOG(ERROR) << "Invalid layout.";
    return nullptr;
  }
  if (!IsValidConfig(format, StorageType::STORAGE_OPAQUE, coded_size,
                     visible_rect, natural_size)) {
    return nullptr;
  }
  auto frame = base::MakeRefCounted<VideoFrame>(
      base::PassKey<VideoFrame>(), *layout, StorageType::STORAGE_OPAQUE,
      visible_rect, natural_size, timestamp);
  frame->metadata().tracking_token = tracking_token;
  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateZeroInitializedFrame(
    VideoPixelFormat format,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp) {
  return CreateFrameInternal(format, coded_size, visible_rect, natural_size,
                             timestamp, true);
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateFrameForNativeTexturesInternal(
    VideoPixelFormat format,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp) {
  if (format != PIXEL_FORMAT_ARGB && format != PIXEL_FORMAT_XRGB &&
      format != PIXEL_FORMAT_NV12 && format != PIXEL_FORMAT_NV16 &&
      format != PIXEL_FORMAT_NV24 && format != PIXEL_FORMAT_NV12A &&
      format != PIXEL_FORMAT_I420 && format != PIXEL_FORMAT_ABGR &&
      format != PIXEL_FORMAT_XBGR && format != PIXEL_FORMAT_XR30 &&
      format != PIXEL_FORMAT_XB30 && format != PIXEL_FORMAT_P010LE &&
      format != PIXEL_FORMAT_P210LE && format != PIXEL_FORMAT_P410LE &&
      format != PIXEL_FORMAT_RGBAF16 && format != PIXEL_FORMAT_YV12 &&
      format != PIXEL_FORMAT_BGRA) {
    DLOG(ERROR) << "Unsupported pixel format: "
                << VideoPixelFormatToString(format);
    return nullptr;
  }
  const StorageType storage = STORAGE_OPAQUE;
  if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(format, storage, coded_size, visible_rect,
                                  natural_size);
    return nullptr;
  }

  auto layout = VideoFrameLayout::Create(format, coded_size);
  if (!layout) {
    DLOG(ERROR) << "Invalid layout.";
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                *layout, storage, visible_rect,
                                                natural_size, timestamp);

  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapSharedImage(
    VideoPixelFormat format,
    scoped_refptr<gpu::ClientSharedImage> shared_image,
    gpu::SyncToken sync_token,
    ReleaseMailboxCB shared_image_release_cb,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp) {
  CHECK(shared_image);
  auto pixel_si_format = VideoPixelFormatToSharedImageFormat(format);
  if (!pixel_si_format.has_value() ||
      pixel_si_format != shared_image->format()) {
    SCOPED_CRASH_KEY_STRING256("video_frame", "format",
                               VideoPixelFormatToString(format));
    SCOPED_CRASH_KEY_STRING256("video_frame", "si_format",
                               shared_image->format().ToString());
    SCOPED_CRASH_KEY_STRING256("video_frame", "si_label",
                               shared_image->debug_label());
    // DUMP_WILL_BE_CHECK(false)
    //     << "VideoFrame format (" << VideoPixelFormatToString(format)
    //     << ") does not match SharedImage format ("
    //     << shared_image->format().ToString() << ")";
  }
  scoped_refptr<VideoFrame> frame = CreateFrameForNativeTexturesInternal(
      format, shared_image->size(), visible_rect, natural_size, timestamp);
  if (!frame) {
    return nullptr;
  }

  frame->acquire_sync_token_ = sync_token;
  frame->set_color_space(shared_image->color_space());
  frame->shared_image_ = shared_image->MakeUnowned();
  if (shared_image_release_cb) {
    frame->SetReleaseMailboxCB(std::move(shared_image_release_cb));
  }
  return frame;
}

scoped_refptr<VideoFrame> VideoFrame::WrapMappableSharedImage(
    scoped_refptr<gpu::ClientSharedImage> shared_image,
    gpu::SyncToken sync_token,
    ReleaseMailboxCB shared_image_release_cb,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp) {
  CHECK(shared_image);

  const std::optional<VideoPixelFormat> format =
      SharedImageFormatToVideoPixelFormat(shared_image->format());
  if (!format) {
    return nullptr;
  }
  constexpr StorageType storage = STORAGE_MAPPABLE_SHARED_IMAGE;
  const gfx::Size& coded_size = shared_image->size();
  if (!IsValidConfig(*format, storage, coded_size, visible_rect,
                     natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config"
                << ConfigToString(*format, storage, coded_size, visible_rect,
                                  natural_size);
    return nullptr;
  }

  const size_t num_planes = shared_image->format().NumberOfPlanes();
  std::vector<ColorPlaneLayout> planes(num_planes);
  for (size_t plane = 0; plane < num_planes; ++plane) {
    planes[plane].stride = shared_image->GetStrideForVideoFrame(plane);
    gfx::Size plane_size = PlaneSizeInSamples(*format, plane, coded_size);
    planes[plane].size =
        (plane_size.height() - 1) * planes[plane].stride +
        plane_size.width() * VideoFrame::BytesPerElement(*format, plane);
  }
  uint64_t modifier = gfx::NativePixmapHandle::kNoModifier;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  if (shared_image->GetGpuMemoryBufferType() ==
      gfx::GpuMemoryBufferType::NATIVE_PIXMAP) {
    const auto gmb_handle = shared_image->CloneGpuMemoryBufferHandle();
    if (gmb_handle.is_null() ||
        gmb_handle.native_pixmap_handle().planes.empty()) {
      DLOG(ERROR) << "Failed to clone the GpuMemoryBufferHandle";
      return nullptr;
    }
    const gfx::NativePixmapHandle& native_pixmap_handle =
        gmb_handle.native_pixmap_handle();
    if (native_pixmap_handle.planes.size() != num_planes) {
      DLOG(ERROR) << "Invalid number of planes="
                  << native_pixmap_handle.planes.size()
                  << ", expected num_planes=" << num_planes;
      return nullptr;
    }
    for (size_t i = 0; i < num_planes; ++i) {
      const auto& plane = native_pixmap_handle.planes[i];
      planes[i].stride = plane.stride;
      planes[i].offset = plane.offset;
      planes[i].size = plane.size;
    }
    modifier = native_pixmap_handle.modifier;
  }
#endif

  const auto layout = VideoFrameLayout::CreateWithPlanes(
      *format, coded_size, std::move(planes),
      VideoFrameLayout::kBufferAddressAlignment, modifier);
  if (!layout) {
    DLOG(ERROR) << __func__ << " Invalid layout";
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                *layout, storage, visible_rect,
                                                natural_size, timestamp);
  if (!frame) {
    DLOG(ERROR) << __func__ << " Couldn't create VideoFrame instance";
    return nullptr;
  }
  if (shared_image_release_cb) {
    frame->SetReleaseMailboxCB(std::move(shared_image_release_cb));
  }
  frame->acquire_sync_token_ = sync_token;

  // Note that we cannot use |shared_image|->MakeUnowned() here since MappableSI
  // owns a MappableBuffer internally, which we cannot create an unowned
  // reference to.
  frame->set_color_space(shared_image->color_space());
  frame->shared_image_ = std::move(shared_image);
  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalData(
    VideoPixelFormat format,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::span<const uint8_t> data,
    base::TimeDelta timestamp) {
  auto layout = GetDefaultLayout(format, coded_size);
  if (!layout) {
    return nullptr;
  }
  return WrapExternalDataWithLayout(*layout, visible_rect, natural_size, data,
                                    timestamp);
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalDataWithLayout(
    const VideoFrameLayout& layout,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::span<const uint8_t> data,
    base::TimeDelta timestamp) {
  StorageType storage_type = STORAGE_UNOWNED_MEMORY;

  if (!IsValidConfig(layout.format(), storage_type, layout.coded_size(),
                     visible_rect, natural_size) ||
      !layout.FitsInContiguousBufferOfSize(data.size())) {
    DLOG(ERROR) << "Invalid config: "
                << ConfigToString(layout.format(), storage_type,
                                  layout.coded_size(), visible_rect,
                                  natural_size);
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(
      base::PassKey<VideoFrame>(), layout, storage_type, visible_rect,
      natural_size, timestamp);

  for (size_t i = 0; i < layout.planes().size(); ++i) {
    auto& plane = layout.planes()[i];
    frame->data_[i] = data.subspan(plane.offset, plane.size);
  }

  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvDataWithLayout(
    const VideoFrameLayout& layout,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::span<const uint8_t> y_data,
    base::span<const uint8_t> u_data,
    base::span<const uint8_t> v_data,
    base::TimeDelta timestamp) {
  const StorageType storage = STORAGE_UNOWNED_MEMORY;
  const VideoPixelFormat format = layout.format();
  if (!IsValidConfig(format, storage, layout.coded_size(), visible_rect,
                     natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(format, storage, layout.coded_size(),
                                  visible_rect, natural_size);
    return nullptr;
  }
  if (!IsYuvPlanar(format) || NumPlanes(format) > 3) {
    DLOG(ERROR) << __func__ << " Format is not YUV. " << format;
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                layout, storage, visible_rect,
                                                natural_size, timestamp);
  std::array<base::span<const uint8_t>, 3> data = {y_data, u_data, v_data};
  for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
    frame->data_[plane] = data[plane];
  }
  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
    VideoPixelFormat format,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    size_t y_stride,
    size_t u_stride,
    size_t v_stride,
    base::span<const uint8_t> y_data,
    base::span<const uint8_t> u_data,
    base::span<const uint8_t> v_data,
    base::TimeDelta timestamp) {
  const StorageType storage = STORAGE_UNOWNED_MEMORY;
  if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(format, storage, coded_size, visible_rect,
                                  natural_size);
    return nullptr;
  }
  if (!IsYuvPlanar(format)) {
    DLOG(ERROR) << __func__ << " Format is not YUV. " << format;
    return nullptr;
  }

  auto layout = VideoFrameLayout::CreateWithStrides(
      format, coded_size, {y_stride, u_stride, v_stride});
  if (!layout) {
    DLOG(ERROR) << "Invalid layout.";
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                *layout, storage, visible_rect,
                                                natural_size, timestamp);
  std::array<base::span<const uint8_t>, 3> data = {y_data, u_data, v_data};
  for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
    frame->data_[plane] = data[plane];
  }
  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
    VideoPixelFormat format,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    size_t y_stride,
    size_t uv_stride,
    base::span<const uint8_t> y_data,
    base::span<const uint8_t> uv_data,
    base::TimeDelta timestamp) {
  const StorageType storage = STORAGE_UNOWNED_MEMORY;
  if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(format, storage, coded_size, visible_rect,
                                  natural_size);
    return nullptr;
  }

  if (NumPlanes(format) != 2) {
    DLOG(ERROR) << "Expecting Y, UV planes to be present for the video format.";
    return nullptr;
  }

  auto layout = VideoFrameLayout::CreateWithStrides(format, coded_size,
                                                    {y_stride, uv_stride});
  if (!layout) {
    DLOG(ERROR) << "Invalid layout";
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                *layout, storage, visible_rect,
                                                natural_size, timestamp);
  std::array<base::span<const uint8_t>, 2> data = {y_data, uv_data};
  for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
    frame->data_[plane] = data[plane];
  }
  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvaData(
    VideoPixelFormat format,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    size_t y_stride,
    size_t u_stride,
    size_t v_stride,
    size_t a_stride,
    base::span<const uint8_t> y_data,
    base::span<const uint8_t> u_data,
    base::span<const uint8_t> v_data,
    base::span<const uint8_t> a_data,
    base::TimeDelta timestamp) {
  const StorageType storage = STORAGE_UNOWNED_MEMORY;
  if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(format, storage, coded_size, visible_rect,
                                  natural_size);
    return nullptr;
  }

  if (NumPlanes(format) != 4) {
    DLOG(ERROR) << "Expecting Y, U, V and A planes to be present for the video"
                << " format.";
    return nullptr;
  }

  auto layout = VideoFrameLayout::CreateWithStrides(
      format, coded_size, {y_stride, u_stride, v_stride, a_stride});
  if (!layout) {
    DLOG(ERROR) << "Invalid layout";
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                *layout, storage, visible_rect,
                                                natural_size, timestamp);
  std::array<base::span<const uint8_t>, 4> data = {y_data, u_data, v_data,
                                                   a_data};
  for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
    frame->data_[plane] = data[plane];
  }
  return frame;
}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalDmabufs(
    const VideoFrameLayout& layout,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    std::vector<base::ScopedFD> dmabuf_fds,
    base::TimeDelta timestamp) {
  const StorageType storage = STORAGE_DMABUFS;
  const VideoPixelFormat format = layout.format();
  const gfx::Size& coded_size = layout.coded_size();
  if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(format, storage, coded_size, visible_rect,
                                  natural_size);
    return nullptr;
  }

  if (dmabuf_fds.empty() || dmabuf_fds.size() > NumPlanes(format)) {
    DLOG(ERROR) << __func__ << " Incorrect number of dmabuf fds provided, got: "
                << dmabuf_fds.size() << ", expected 1 to " << NumPlanes(format);
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                layout, storage, visible_rect,
                                                natural_size, timestamp);
  if (!frame) {
    DLOG(ERROR) << __func__ << " Couldn't create VideoFrame instance.";
    return nullptr;
  }

  frame->shared_image_release_cb_ = ReleaseMailboxCBWithLostResource();
  frame->dmabuf_fds_ = std::move(dmabuf_fds);
  DCHECK(frame->HasDmaBufs());

  return frame;
}
#endif

#if BUILDFLAG(IS_APPLE)
// static
scoped_refptr<VideoFrame> VideoFrame::WrapUnacceleratedIOSurface(
    gfx::GpuMemoryBufferHandle handle,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp) {
  if (handle.type != gfx::GpuMemoryBufferType::IO_SURFACE_BUFFER) {
    DLOG(ERROR) << "Non-IOSurface handle.";
    return nullptr;
  }

  gfx::ScopedIOSurface io_surface = handle.io_surface();
  if (!io_surface) {
    return nullptr;
  }

  // Only support NV12 IOSurfaces.
  const OSType cv_pixel_format = IOSurfaceGetPixelFormat(io_surface.get());
  if (cv_pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
    DLOG(ERROR) << "Invalid (non-NV12) pixel format.";
    return nullptr;
  }
  const VideoPixelFormat pixel_format = PIXEL_FORMAT_NV12;

  // Retrieve the layout parameters for |io_surface_|.
  const size_t num_planes = IOSurfaceGetPlaneCount(io_surface.get());
  const gfx::Size size(IOSurfaceGetWidth(io_surface.get()),
                       IOSurfaceGetHeight(io_surface.get()));
  std::vector<size_t> strides;
  for (size_t i = 0; i < num_planes; ++i)
    strides.push_back(IOSurfaceGetBytesPerRowOfPlane(io_surface.get(), i));
  std::optional<VideoFrameLayout> layout =
      media::VideoFrameLayout::CreateWithStrides(pixel_format, size, strides);
  if (!layout) {
    DLOG(ERROR) << "Invalid layout.";
    return nullptr;
  }

  const StorageType storage_type = STORAGE_UNOWNED_MEMORY;
  if (!IsValidConfig(pixel_format, storage_type, size, visible_rect,
                     natural_size)) {
    DLOG(ERROR) << "Invalid config.";
    return nullptr;
  }

  // Lock the IOSurface for CPU read access. After the VideoFrame is created,
  // add a destruction callback to unlock the IOSurface.
  kern_return_t lock_result =
      IOSurfaceLock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr);
  if (lock_result != KERN_SUCCESS) {
    DLOG(ERROR) << "Failed to lock IOSurface.";
    return nullptr;
  }
  auto unlock_lambda =
      [](base::apple::ScopedCFTypeRef<IOSurfaceRef> io_surface) {
        IOSurfaceUnlock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr);
      };

  auto frame = base::MakeRefCounted<VideoFrame>(
      base::PassKey<VideoFrame>(), *layout, storage_type, visible_rect,
      natural_size, timestamp);
  for (size_t i = 0; i < num_planes; ++i) {
    uint8_t* plane_data = reinterpret_cast<uint8_t*>(
        IOSurfaceGetBaseAddressOfPlane(io_surface.get(), i));
    size_t rows = IOSurfaceGetHeightOfPlane(io_surface.get(), i);
    size_t plane_size = rows * strides[i];
    // SAFETY: IOSurface has data allocated for each plane,
    // the size of the plane is IOSurfaceGetHeightOfPlane() *
    // IOSurfaceGetBytesPerRowOfPlane().
    frame->data_[i] = UNSAFE_BUFFERS(base::span(plane_data, plane_size));
  }
  frame->AddDestructionObserver(
      base::BindOnce(unlock_lambda, std::move(io_surface)));
  return frame;
}
#endif

// static
scoped_refptr<VideoFrame> VideoFrame::WrapVideoFrame(
    scoped_refptr<VideoFrame> frame,
    VideoPixelFormat format,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size) {
  if (!AreValidPixelFormatsForWrap(frame->format(), format)) {
    DLOG(ERROR) << __func__ << " Invalid format conversion."
                << VideoPixelFormatToString(frame->format()) << " to "
                << VideoPixelFormatToString(format);
    return nullptr;
  }

  if (!IsValidConfig(format, frame->storage_type(), frame->coded_size(),
                     visible_rect, natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(format, frame->storage_type(),
                                  frame->coded_size(), visible_rect,
                                  natural_size);
    return nullptr;
  }

  size_t new_plane_count = NumPlanes(format);
  std::optional<VideoFrameLayout> new_layout;
  if (format == frame->format()) {
    new_layout = frame->layout();
  } else {
    std::vector<ColorPlaneLayout> new_planes = frame->layout().planes();
    if (new_plane_count > new_planes.size()) {
      DLOG(ERROR) << " Wrapping frame has more planes than old one."
                  << " old plane count: " << new_planes.size()
                  << " new plane count: " << new_plane_count;
      return nullptr;
    }
    new_planes.resize(new_plane_count);
    new_layout = VideoFrameLayout::CreateWithPlanes(format, frame->coded_size(),
                                                    new_planes);
  }

  if (!new_layout.has_value()) {
    DLOG(ERROR) << " Can't create layout for the wrapping frame";
    return nullptr;
  }

  auto wrapping_frame = base::MakeRefCounted<VideoFrame>(
      base::PassKey<VideoFrame>(), new_layout.value(), frame->storage_type(),
      visible_rect, natural_size, frame->timestamp());

  // Copy all metadata to the wrapped frame->
  wrapping_frame->metadata().MergeMetadataFrom(frame->metadata());
  wrapping_frame->set_color_space(frame->ColorSpace());
  wrapping_frame->set_hdr_metadata(frame->hdr_metadata());

  if (frame->HasDirectCpuAccess()) {
    for (size_t i = 0; i < new_plane_count; ++i) {
      wrapping_frame->data_[i] = frame->data_[i];
    }
  }

  if (frame->storage_type() == STORAGE_SHMEM) {
    DCHECK(frame->shm_region_ && frame->shm_region_->IsValid());
    wrapping_frame->BackWithSharedMemory(frame->shm_region_);
  }

  // Don't let a Matryoshka doll of frames occur. Do this here instead of above
  // since |frame| may have different metadata than |frame->wrapped_frame_|.
  //
  // We must still keep |frame| alive though since it may have destruction
  // observers which signal that the underlying resource is okay to reuse. E.g.,
  // VideoFramePool. That's why we put it into |intermediate_wrapped_frame_|.
  if (frame->wrapped_frame_) {
    wrapping_frame->intermediate_wrapped_frame_ = frame;
    frame = frame->wrapped_frame_;
  }

  wrapping_frame->wrapped_frame_ = std::move(frame);
  return wrapping_frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateEOSFrame() {
  auto layout = VideoFrameLayout::Create(PIXEL_FORMAT_UNKNOWN, gfx::Size());
  if (!layout) {
    DLOG(ERROR) << "Invalid layout.";
    return nullptr;
  }
  auto frame = base::MakeRefCounted<VideoFrame>(
      base::PassKey<VideoFrame>(), *layout, STORAGE_UNKNOWN, gfx::Rect(),
      gfx::Size(), kNoTimestamp, FrameControlType::kEos);
  frame->metadata().end_of_stream = true;
  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateColorFrame(
    const gfx::Size& size,
    uint8_t y,
    uint8_t u,
    uint8_t v,
    base::TimeDelta timestamp) {
  scoped_refptr<VideoFrame> frame =
      CreateFrame(PIXEL_FORMAT_I420, size, gfx::Rect(size), size, timestamp);
  if (frame)
    FillYUV(frame.get(), y, u, v);
  return frame;
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateBlackFrame(const gfx::Size& size) {
  const uint8_t kBlackY = 0x00;
  const uint8_t kBlackUV = 0x80;
  const base::TimeDelta kZero;
  return CreateColorFrame(size, kBlackY, kBlackUV, kBlackUV, kZero);
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateTransparentFrame(
    const gfx::Size& size) {
  const uint8_t kBlackY = 0x00;
  const uint8_t kBlackUV = 0x00;
  const uint8_t kTransparentA = 0x00;
  const base::TimeDelta kZero;
  scoped_refptr<VideoFrame> frame =
      CreateFrame(PIXEL_FORMAT_I420A, size, gfx::Rect(size), size, kZero);
  if (frame)
    FillYUVA(frame.get(), kBlackY, kBlackUV, kBlackUV, kTransparentA);
  return frame;
}

// static
size_t VideoFrame::NumPlanes(VideoPixelFormat format) {
  return VideoFrameLayout::NumPlanes(format);
}

// static
size_t VideoFrame::AllocationSize(VideoPixelFormat format,
                                  const gfx::Size& coded_size) {
  base::CheckedNumeric<size_t> total = 0;
  for (size_t i = 0; i < NumPlanes(format); ++i) {
    total += PlaneSize(format, i, coded_size).Area64();
  }

  // If the line below is crashing, someone has likely added a pixel format with
  // more than 8 bytes per pixel.
  return total.ValueOrDie();
}

// static
gfx::Size VideoFrame::PlaneSize(VideoPixelFormat format,
                                size_t plane,
                                const gfx::Size& coded_size) {
  gfx::Size size = PlaneSizeInSamples(format, plane, coded_size);
  size.set_width(size.width() * BytesPerElement(format, plane));
  return size;
}

// static
gfx::Size VideoFrame::PlaneSizeInSamples(VideoPixelFormat format,
                                         size_t plane,
                                         const gfx::Size& coded_size) {
  DCHECK(IsValidPlane(format, plane));
  const gfx::Size subsample_size = SampleSize(format, plane);
  return gfx::Size(base::bits::AlignUpDeprecatedDoNotUse(
                       coded_size.width(), subsample_size.width()) /
                       subsample_size.width(),
                   base::bits::AlignUpDeprecatedDoNotUse(
                       coded_size.height(), subsample_size.height()) /
                       subsample_size.height());
}

// static
int VideoFrame::PlaneHorizontalBitsPerPixel(VideoPixelFormat format,
                                            size_t plane) {
  DCHECK(IsValidPlane(format, plane));
  const int bits_per_element = 8 * BytesPerElement(format, plane);
  const int horiz_pixels_per_element = SampleSize(format, plane).width();
  DCHECK_EQ(bits_per_element % horiz_pixels_per_element, 0);
  return bits_per_element / horiz_pixels_per_element;
}

// static
int VideoFrame::PlaneBitsPerPixel(VideoPixelFormat format, size_t plane) {
  DCHECK(IsValidPlane(format, plane));
  return PlaneHorizontalBitsPerPixel(format, plane) /
         SampleSize(format, plane).height();
}

// static
size_t VideoFrame::RowBytes(size_t plane, VideoPixelFormat format, int width) {
  DCHECK(IsValidPlane(format, plane));
  return BytesPerElement(format, plane) * Columns(plane, format, width);
}

// static
int VideoFrame::BytesPerElement(VideoPixelFormat format, size_t plane) {
  DCHECK(IsValidPlane(format, plane));
  switch (format) {
    case PIXEL_FORMAT_RGBAF16:
      return 8;
    case PIXEL_FORMAT_ARGB:
    case PIXEL_FORMAT_BGRA:
    case PIXEL_FORMAT_XRGB:
    case PIXEL_FORMAT_ABGR:
    case PIXEL_FORMAT_XBGR:
    case PIXEL_FORMAT_XR30:
    case PIXEL_FORMAT_XB30:
      return 4;
    case PIXEL_FORMAT_RGB24:
      return 3;
    case PIXEL_FORMAT_Y16:
    case PIXEL_FORMAT_UYVY:
    case PIXEL_FORMAT_YUY2:
    case PIXEL_FORMAT_YUV420P10:
    case PIXEL_FORMAT_YUV422P10:
    case PIXEL_FORMAT_YUV444P10:
    case PIXEL_FORMAT_YUV420P12:
    case PIXEL_FORMAT_YUV422P12:
    case PIXEL_FORMAT_YUV444P12:
    case PIXEL_FORMAT_YUV420AP10:
    case PIXEL_FORMAT_YUV422AP10:
    case PIXEL_FORMAT_YUV444AP10:
      return 2;
    case PIXEL_FORMAT_NV12:
    case PIXEL_FORMAT_NV16:
    case PIXEL_FORMAT_NV21:
    case PIXEL_FORMAT_NV24: {
      constexpr auto bytes_per_element = std::to_array({1, 2});
      DCHECK_LT(plane, std::size(bytes_per_element));
      return bytes_per_element[plane];
    }
    case PIXEL_FORMAT_NV12A: {
      constexpr auto bytes_per_element = std::to_array({1, 2, 1});
      DCHECK_LT(plane, std::size(bytes_per_element));
      return bytes_per_element[plane];
    }
    case PIXEL_FORMAT_P010LE:
    case PIXEL_FORMAT_P210LE:
    case PIXEL_FORMAT_P410LE: {
      constexpr auto bytes_per_element = std::to_array({1, 2});
      DCHECK_LT(plane, std::size(bytes_per_element));
      return bytes_per_element[plane] * 2;
    }
    case PIXEL_FORMAT_YV12:
    case PIXEL_FORMAT_I420:
    case PIXEL_FORMAT_I422:
    case PIXEL_FORMAT_I420A:
    case PIXEL_FORMAT_I444:
    case PIXEL_FORMAT_I422A:
    case PIXEL_FORMAT_I444A:
      return 1;
    case PIXEL_FORMAT_MJPEG:
      return 0;
    case PIXEL_FORMAT_UNKNOWN:
      break;
  }
  NOTREACHED();
}

// static
std::vector<size_t> VideoFrame::ComputeStrides(VideoPixelFormat format,
                                               const gfx::Size& coded_size) {
  std::vector<size_t> strides;
  const size_t num_planes = NumPlanes(format);
  if (num_planes == 1) {
    strides.push_back(RowBytes(0, format, coded_size.width()));
  } else {
    for (size_t plane = 0; plane < num_planes; ++plane) {
      strides.push_back(base::bits::AlignUp(
          RowBytes(plane, format, coded_size.width()), kFrameAddressAlignment));
    }
  }
  return strides;
}

// static
size_t VideoFrame::Rows(size_t plane, VideoPixelFormat format, int height) {
  DCHECK(IsValidPlane(format, plane));
  const int sample_height = SampleSize(format, plane).height();
  return base::bits::AlignUpDeprecatedDoNotUse(height, sample_height) /
         sample_height;
}

// static
size_t VideoFrame::Columns(size_t plane, VideoPixelFormat format, int width) {
  DCHECK(IsValidPlane(format, plane));
  const int sample_width = SampleSize(format, plane).width();
  return base::bits::AlignUpDeprecatedDoNotUse(width, sample_width) /
         sample_width;
}

// static
void VideoFrame::UpdateHashWithFrameForTesting(crypto::hash::Hasher& hasher,
                                               const VideoFrame& frame,
                                               bool visible_data_only) {
  for (size_t plane = 0; plane < NumPlanes(frame.format()); ++plane) {
    const size_t rows = base::checked_cast<size_t>(
        visible_data_only ? frame.GetVisibleRows(plane) : frame.rows(plane));
    const size_t row_bytes = base::checked_cast<size_t>(
        visible_data_only ? frame.GetVisibleRowBytes(plane)
                          : frame.row_bytes(plane));
    const auto& plane_data = frame.data_[plane];
    for (size_t row = 0; row < rows; ++row) {
      hasher.Update(plane_data.subspan(frame.stride(plane) * row, row_bytes));
    }
  }
}

// static
std::string VideoFrame::HexHashOfFrameForTesting(const VideoFrame& frame,
                                                 bool visible_data_only) {
  crypto::hash::Hasher hasher(crypto::hash::HashKind::kSha256);
  UpdateHashWithFrameForTesting(hasher, frame, visible_data_only);  // IN-TEST
  std::array<uint8_t, crypto::hash::kSha256Size> hash;
  hasher.Finish(hash);
  return base::HexEncodeLower(hash);
}

void VideoFrame::BackWithSharedMemory(
    const base::ReadOnlySharedMemoryRegion* region) {
  DCHECK(!shm_region_);
  DCHECK(!owned_shm_region_.IsValid());
  // Either we should be backing a frame created with WrapExternal*, or we are
  // wrapping an existing STORAGE_SHMEM, in which case the storage type has
  // already been set to STORAGE_SHMEM.
  DCHECK(storage_type_ == STORAGE_UNOWNED_MEMORY ||
         storage_type_ == STORAGE_SHMEM);
  DCHECK(region && region->IsValid());
  storage_type_ = STORAGE_SHMEM;
  shm_region_ = region;
}

void VideoFrame::BackWithOwnedSharedMemory(
    base::ReadOnlySharedMemoryRegion region,
    base::ReadOnlySharedMemoryMapping mapping) {
  DCHECK(!shm_region_);
  DCHECK(!owned_shm_region_.IsValid());
  // We should be backing a frame created with WrapExternal*. We cannot be
  // wrapping an existing STORAGE_SHMEM, as the region is unowned in that case.
  DCHECK(storage_type_ == STORAGE_UNOWNED_MEMORY);
  storage_type_ = STORAGE_SHMEM;
  owned_shm_region_ = std::move(region);
  shm_region_ = &owned_shm_region_;
  owned_shm_mapping_ = std::move(mapping);
}

bool VideoFrame::HasDirectCpuAccess() const {
  return StorageTypeAllowsDirectCpuAccess(storage_type_);
}

bool VideoFrame::HasSharedImage() const {
  return wrapped_frame_ ? wrapped_frame_->HasSharedImage()
                        : shared_image_ != nullptr;
}

bool VideoFrame::HasMappableSharedImage() const {
  return storage_type_ == STORAGE_MAPPABLE_SHARED_IMAGE;
}

bool VideoFrame::HasNativeMappableSharedImage() const {
  if (wrapped_frame_) {
    return wrapped_frame_->HasNativeMappableSharedImage();
  }

  if (HasMappableSharedImage()) {
    CHECK(shared_image_);
    return !shared_image_->IsSharedMemoryForVideoFrame();
  }

  return false;
}

bool VideoFrame::AsyncMappingIsNonBlocking() const {
  if (wrapped_frame_) {
    return wrapped_frame_->AsyncMappingIsNonBlocking();
  }
  CHECK(HasMappableSharedImage());
  CHECK(shared_image_);
  return shared_image_->AsyncMappingIsNonBlocking();
}

gfx::GpuMemoryBufferHandle VideoFrame::GetGpuMemoryBufferHandle() const {
  if (wrapped_frame_) {
    return wrapped_frame_->GetGpuMemoryBufferHandle();
  }
  if (HasMappableSharedImage()) {
    // If MappableSI is used, there must be a shared image.
    CHECK(HasSharedImage());
    return shared_image_->CloneGpuMemoryBufferHandle();
  }
  return gfx::GpuMemoryBufferHandle();
}

bool VideoFrame::IsSameAllocation(VideoPixelFormat format,
                                  const gfx::Size& coded_size,
                                  const gfx::Rect& visible_rect,
                                  const gfx::Size& natural_size) const {
  // CreateFrameInternal() changes coded_size to new_coded_size. Match that
  // behavior here.
  const gfx::Size new_coded_size = DetermineAlignedSize(format, coded_size);
  return this->format() == format && this->coded_size() == new_coded_size &&
         visible_rect_ == visible_rect && natural_size_ == natural_size;
}

gfx::ColorSpace VideoFrame::ColorSpace() const {
  return color_space_;
}

void VideoFrame::set_color_space(const gfx::ColorSpace& color_space) {
  // Check color spaces are same for video frames created from shared image.
  if (HasSharedImage()) {
    SCOPED_CRASH_KEY_STRING256("video_frame", "si_color_space",
                               shared_image()->color_space().ToString());
    SCOPED_CRASH_KEY_STRING256("video_frame", "color_space",
                               color_space.ToString());
    SCOPED_CRASH_KEY_STRING256("video_frame", "si_label",
                               shared_image()->debug_label());
    CHECK_EQ(color_space, shared_image()->color_space(),
             base::NotFatalUntil::M154);
  }
  color_space_ = color_space;
}

gfx::ColorSpace VideoFrame::CompatRGBColorSpace() const {
  const auto rgb_color_space = ColorSpace().GetAsFullRangeRGB();
  if (!rgb_color_space.IsValid()) {
    return gfx::ColorSpace::CreateSRGB();
  }

  auto primary_id = rgb_color_space.GetPrimaryID();
  switch (primary_id) {
    case gfx::ColorSpace::PrimaryID::CUSTOM:
      return rgb_color_space;
    case gfx::ColorSpace::PrimaryID::SMPTE170M:
    case gfx::ColorSpace::PrimaryID::SMPTE240M:
      primary_id = gfx::ColorSpace::PrimaryID::BT709;
      break;
    default:
      break;
  }
  auto transfer_id = rgb_color_space.GetTransferID();
  switch (transfer_id) {
    case gfx::ColorSpace::TransferID::CUSTOM:
    case gfx::ColorSpace::TransferID::CUSTOM_HDR:
      return rgb_color_space;
    case gfx::ColorSpace::TransferID::BT709_APPLE:
    case gfx::ColorSpace::TransferID::SMPTE170M:
    case gfx::ColorSpace::TransferID::SMPTE240M:
      transfer_id = gfx::ColorSpace::TransferID::SRGB;
      break;
    default:
      break;
  }
  return gfx::ColorSpace(primary_id, transfer_id);
}

int VideoFrame::row_bytes(size_t plane) const {
  return RowBytes(plane, format(), coded_size().width());
}

int VideoFrame::rows(size_t plane) const {
  return Rows(plane, format(), coded_size().height());
}

int VideoFrame::columns(size_t plane) const {
  return Columns(plane, format(), coded_size().width());
}

int VideoFrame::GetVisibleRowBytes(size_t plane) const {
  return RowBytes(plane, format(), visible_rect().width());
}

int VideoFrame::GetVisibleRows(size_t plane) const {
  return Rows(plane, format(), visible_rect().height());
}

int VideoFrame::GetVisibleColumns(size_t plane) const {
  return Columns(plane, format(), visible_rect().width());
}

template <typename T>
base::span<T> VideoFrame::GetVisibleDataInternal(base::span<T> data,
                                                 size_t plane) const {
  DCHECK(IsValidPlane(format(), plane));
  DCHECK(HasDirectCpuAccess());
  if (data.empty()) [[unlikely]] {
    return {};
  }

  // Calculate an offset that is properly aligned for all planes.
  const gfx::Size alignment = CommonAlignment(format());
  const gfx::Point offset(base::bits::AlignDownDeprecatedDoNotUse(
                              visible_rect_.x(), alignment.width()),
                          base::bits::AlignDownDeprecatedDoNotUse(
                              visible_rect_.y(), alignment.height()));

  const size_t plane_stride = stride(plane);
  const gfx::Size subsample = SampleSize(format(), plane);
  DCHECK(offset.x() % subsample.width() == 0);
  DCHECK(offset.y() % subsample.height() == 0);

  // Use CheckedNumeric for offset calculation to prevent overflow.
  // Row offset.
  base::CheckedNumeric<size_t> checked_offset = plane_stride;
  checked_offset *= base::checked_cast<size_t>(offset.y() / subsample.height());
  // Column offset.
  checked_offset += base::checked_cast<size_t>(
      BytesPerElement(format(), plane) * (offset.x() / subsample.width()));
  const size_t visible_plane_offset = checked_offset.ValueOrDie();

  const size_t visible_rows = base::checked_cast<size_t>(GetVisibleRows(plane));
  const size_t visible_row_bytes =
      base::checked_cast<size_t>(GetVisibleRowBytes(plane));

  // Use CheckedNumeric for size calculation to prevent overflow.
  // In the last row, bytes between visible width and the full stride are not
  // the part of the visible plane.
  base::CheckedNumeric<size_t> checked_visible_plane_size = 0;
  if (visible_rows > 0) {
    checked_visible_plane_size = plane_stride;
    checked_visible_plane_size *= (visible_rows - 1);
    checked_visible_plane_size += visible_row_bytes;
  }
  const size_t visible_plane_size = checked_visible_plane_size.ValueOrDie();

  return data.subspan(visible_plane_offset, visible_plane_size);
}

base::span<const uint8_t> VideoFrame::GetVisiblePlaneData(size_t plane) const {
  return GetVisibleDataInternal(data_[plane], plane);
}

base::span<uint8_t> VideoFrame::GetWritableVisiblePlaneData(size_t plane) {
  // TODO(crbug.com/40265179): Also CHECK that the storage type isn't
  // STORAGE_UNOWNED_MEMORY once non-compliant usages are fixed.
  CHECK_NE(storage_type_, STORAGE_SHMEM);
  auto const_span = data_[plane];
  // SAFETY: We take data() and size() from another span, which supposedly
  // refers to a valid range in memory.
  auto non_const_span = UNSAFE_BUFFERS(
      base::span(const_cast<uint8_t*>(const_span.data()), const_span.size()));
  return GetVisibleDataInternal(non_const_span, plane);
}

const uint8_t* VideoFrame::visible_data(size_t plane) const {
  return GetVisiblePlaneData(plane).data();
}

uint8_t* VideoFrame::GetWritableVisibleData(size_t plane) {
  return GetWritableVisiblePlaneData(plane).data();
}

SkYUVAInfo VideoFrame::GetVisibleSkYUVAInfo() const {
  const auto plane_config = SkYUVAPlaneConfigForFormat(format());
  if (plane_config == SkYUVAInfo::PlaneConfig::kUnknown) {
    return {};
  }
  const auto subsampling = SkYUVASubsamplingForFormat(format());
  if (subsampling == SkYUVAInfo::Subsampling::kUnknown) {
    return {};
  }
  SkYUVColorSpace yuv_color_space = kRec601_Limited_SkYUVColorSpace;
  if (!ColorSpace().ToSkYUVColorSpace(static_cast<int>(BitDepth()),
                                      &yuv_color_space)) {
    // Guess that the matrix is SkYUVColorSpace is Rec601 rather than
    // failing. This matches the default behavior of the historical use
    // of libyuv.
    DLOG(ERROR) << "Invalid or unspecified matrix, assuming Rec601.";
    yuv_color_space = kRec601_Limited_SkYUVColorSpace;
  }
  return SkYUVAInfo(
      SkISize::Make(visible_rect_.width(), visible_rect_.height()),
      plane_config, subsampling, yuv_color_space);
}

std::vector<SkPixmap> VideoFrame::GetVisiblePlanesSkPixmaps() const {
  const auto yuva_info = GetVisibleSkYUVAInfo();
  const auto color_space = ColorSpace().GetAsFullRangeRGB().ToSkColorSpace();
  std::array<SkISize, kMaxPlanes> plane_dimensions = {
      SkISize::Make(visible_rect_.width(), visible_rect_.height()),
  };
  if (yuva_info.isValid()) {
    yuva_info.planeDimensions(plane_dimensions.data());
    DCHECK_EQ(NumPlanes(format()), static_cast<size_t>(yuva_info.numPlanes()));
  } else {
    DCHECK_EQ(NumPlanes(format()), 1u);
  }

  std::vector<SkPixmap> planes(NumPlanes(format()));
  for (size_t p = 0; p < planes.size(); ++p) {
    const auto color_type = SkColorTypeForPlaneNoCheck(format(), p);
    if (color_type == kUnknown_SkColorType) {
      return {};
    }
    const auto alpha_type = SkColorTypeIsAlwaysOpaque(color_type)
                                ? kOpaque_SkAlphaType
                                : kUnpremul_SkAlphaType;
    const SkImageInfo plane_info = SkImageInfo::Make(
        plane_dimensions[p], color_type, alpha_type, color_space);
    planes[p] = SkPixmap(plane_info, visible_data(p), stride(p));
  }
  return planes;
}

gpu::SyncToken VideoFrame::acquire_sync_token() const {
  CHECK(HasSharedImage());
  return wrapped_frame_ ? wrapped_frame_->acquire_sync_token()
                        : acquire_sync_token_;
}

scoped_refptr<gpu::ClientSharedImage> VideoFrame::shared_image() const {
  CHECK(HasSharedImage());
  return wrapped_frame_ ? wrapped_frame_->shared_image() : shared_image_;
}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
size_t VideoFrame::NumDmabufFds() const {
  if (wrapped_frame_) {
    return wrapped_frame_->NumDmabufFds();
  }
  return dmabuf_fds_.size();
}

bool VideoFrame::HasDmaBufs() const {
  return NumDmabufFds() > 0;
}

int VideoFrame::GetDmabufFd(size_t i) const {
  if (wrapped_frame_) {
    return wrapped_frame_->GetDmabufFd(i);
  }

  DCHECK_EQ(storage_type_, STORAGE_DMABUFS);
  return dmabuf_fds_[i].get();
}
#endif

void VideoFrame::SetReleaseMailboxCB(ReleaseMailboxCB release_mailbox_cb) {
  DCHECK(release_mailbox_cb);
  DCHECK(!shared_image_release_cb_);
  // We don't relay SetReleaseMailboxCB to |wrapped_frame_| because the method
  // is not thread safe.  This method should only be called by the owner of
  // |wrapped_frame_| directly.
  DCHECK(!wrapped_frame_);
  // Binds `release_mailbox_cb` and runs it with SyncToken ignoring the bool.
  // Returns a OnceCallback<void (const gpu::SyncToken &, bool)> which is then
  // stored.
  SetReleaseMailboxCB(
      base::BindOnce([](ReleaseMailboxCB cb, const gpu::SyncToken& sync_token,
                        bool lost_resource) { std::move(cb).Run(sync_token); },
                     std::move(release_mailbox_cb)));
}

void VideoFrame::SetReleaseMailboxCB(
    ReleaseMailboxCBWithLostResource release_mailbox_cb) {
  DCHECK(release_mailbox_cb);
  DCHECK(!shared_image_release_cb_);
  // We don't relay SetReleaseMailboxCB to |wrapped_frame_| because the method
  // is not thread safe.  This method should only be called by the owner of
  // |wrapped_frame_| directly.
  DCHECK(!wrapped_frame_);
  shared_image_release_cb_ = std::move(release_mailbox_cb);
}

bool VideoFrame::HasReleaseMailboxCB() const {
  return wrapped_frame_ ? wrapped_frame_->HasReleaseMailboxCB()
                        : !!shared_image_release_cb_;
}

void VideoFrame::SetLostSharedImageResource() {
  if (wrapped_frame_) {
    wrapped_frame_->SetLostSharedImageResource();
    return;
  }
  lost_shared_image_resource_ = true;
}

void VideoFrame::AddDestructionObserver(base::OnceClosure callback) {
  DCHECK(!callback.is_null());
  base::AutoLock lock(done_callbacks_lock_);
  done_callbacks_.push_back(std::move(callback));
}

gpu::SyncToken VideoFrame::UpdateReleaseSyncToken(SyncTokenClient* client) {
  DCHECK(HasSharedImage());
  if (wrapped_frame_) {
    return wrapped_frame_->UpdateReleaseSyncToken(client);
  }
  base::AutoLock locker(release_sync_token_lock_);
  // Must wait on the previous sync point before inserting a new sync point so
  // that |shared_image_release_cb_| guarantees the previous sync
  // point occurred when it waits on |release_sync_token_|.
  if (release_sync_token_.HasData())
    client->WaitSyncToken(release_sync_token_);
  client->GenerateSyncToken(&release_sync_token_);
  return release_sync_token_;
}

void VideoFrame::UpdateAcquireSyncToken(gpu::SyncToken token) {
  DCHECK(HasSharedImage());
  if (wrapped_frame_) {
    return wrapped_frame_->UpdateAcquireSyncToken(token);
  }
  base::AutoLock locker(release_sync_token_lock_);
  acquire_sync_token_ = token;
}

std::string VideoFrame::AsHumanReadableString() const {
  if (metadata().end_of_stream) {
    return "end of stream";
  }

  std::ostringstream s;
  s << ConfigToString(format(), storage_type_, coded_size(), visible_rect_,
                      natural_size_)
    << " timestamp:" << timestamp_.InMicroseconds()
    << " color_space: " << ColorSpace().ToString()
    << " hdr_metadata: " << hdr_metadata_.ToString();
  if (HasSharedImage()) {
    s << " shared_image: {"
      << " format: " << shared_image()->format().ToString()
      << " usage: " << shared_image()->usage().ToString()
      << " label: " << shared_image()->debug_label() << " }";
  }
  return s.str();
}

size_t VideoFrame::BitDepth() const {
  return media::BitDepth(format());
}

VideoFrame::VideoFrame(base::PassKey<VideoFrame>,
                       const VideoFrameLayout& layout,
                       StorageType storage_type,
                       const gfx::Rect& visible_rect,
                       const gfx::Size& natural_size,
                       base::TimeDelta timestamp,
                       FrameControlType frame_control_type)
    : layout_(layout),
      storage_type_(storage_type),
      visible_rect_(Intersection(visible_rect, gfx::Rect(layout.coded_size()))),
      natural_size_(natural_size),
      timestamp_(timestamp),
      unique_id_(GetNextID()) {
  CHECK(IsValidConfigInternal(format(), frame_control_type, coded_size(),
                              visible_rect_, natural_size_));
  DCHECK(visible_rect_ == visible_rect)
      << "visible_rect " << visible_rect.ToString() << " exceeds coded_size "
      << coded_size().ToString();
}

VideoFrame::~VideoFrame() {
  if (shared_image_release_cb_) {
    gpu::SyncToken release_sync_token;
    {
      // To ensure that changes to |release_sync_token_| are visible on this
      // thread (imply a memory barrier).
      base::AutoLock locker(release_sync_token_lock_);
      release_sync_token = release_sync_token_;
    }
    std::move(shared_image_release_cb_)
        .Run(release_sync_token, lost_shared_image_resource_);
  }
  // Prevents dangling raw ptr, see https://docs.google.com/document/d/156O7kBZqIhe1dUcqTMcN5T-6YEAcg0yNnj5QlnZu9xU/edit?usp=sharing.
  shm_region_ = nullptr;

  std::vector<base::OnceClosure> done_callbacks;
  {
    base::AutoLock lock(done_callbacks_lock_);
    done_callbacks = std::move(done_callbacks_);
  }
  for (auto& callback : done_callbacks) {
    std::move(callback).Run();
  }

  // This flattens the call graph avoiding recursion while walking
  // `intermediate_wrapped_frame_` pointer chain, otherwise we might get
  // a stack overflow while deleting the whole chain of nested frames.
  auto frame_to_release = std::move(intermediate_wrapped_frame_);
  // Delete all the frames for which `intermediate_wrapped_frame_` is
  // the only reference.
  while (frame_to_release && frame_to_release->HasOneRef()) {
    auto next_frame = std::move(frame_to_release->intermediate_wrapped_frame_);
    frame_to_release = next_frame;
  }
}

// static
std::string VideoFrame::ConfigToString(const VideoPixelFormat format,
                                       const StorageType storage_type,
                                       const gfx::Size& coded_size,
                                       const gfx::Rect& visible_rect,
                                       const gfx::Size& natural_size) {
  return base::StringPrintf(
      "format:%s storage_type:%s coded_size:%s visible_rect:%s natural_size:%s",
      VideoPixelFormatToString(format).c_str(),
      StorageTypeToString(storage_type).c_str(), coded_size.ToString().c_str(),
      visible_rect.ToString().c_str(), natural_size.ToString().c_str());
}

// static
gfx::Size VideoFrame::DetermineAlignedSize(VideoPixelFormat format,
                                           const gfx::Size& dimensions) {
  const gfx::Size alignment = CommonAlignment(format);
  const gfx::Size adjusted =
      gfx::Size(base::bits::AlignUpDeprecatedDoNotUse(dimensions.width(),
                                                      alignment.width()),
                base::bits::AlignUpDeprecatedDoNotUse(dimensions.height(),
                                                      alignment.height()));
  DCHECK((adjusted.width() % alignment.width() == 0) &&
         (adjusted.height() % alignment.height() == 0));
  return adjusted;
}

// static
bool VideoFrame::IsValidSize(const gfx::Size& coded_size,
                             const gfx::Rect& visible_rect,
                             const gfx::Size& natural_size) {
  return IsValidCodedSize(coded_size) && IsValidCodedSize(natural_size) &&
         !(visible_rect.x() < 0 || visible_rect.y() < 0 ||
           visible_rect.right() > coded_size.width() ||
           visible_rect.bottom() > coded_size.height());
}

// static
bool VideoFrame::IsValidCodedSize(const gfx::Size& size) {
  const int size_area = size.GetCheckedArea().ValueOrDefault(INT_MAX);
  static_assert(limits::kMaxCanvas < INT_MAX, "");
  return size_area <= limits::kMaxCanvas &&
         size.width() <= limits::kMaxDimension &&
         size.height() <= limits::kMaxDimension;
}

// static
bool VideoFrame::IsValidConfigInternal(VideoPixelFormat format,
                                       FrameControlType frame_control_type,
                                       const gfx::Size& coded_size,
                                       const gfx::Rect& visible_rect,
                                       const gfx::Size& natural_size) {
  // Check maximum limits for all formats.
  if (!IsValidSize(coded_size, visible_rect, natural_size)) {
    return false;
  }

  switch (frame_control_type) {
    case FrameControlType::kNone:
      // Check that software-allocated buffer formats are not empty.
      return !coded_size.IsEmpty() && !visible_rect.IsEmpty() &&
             !natural_size.IsEmpty();
    case FrameControlType::kEos:
      DCHECK_EQ(format, PIXEL_FORMAT_UNKNOWN);
      return coded_size.IsEmpty() && visible_rect.IsEmpty() &&
             natural_size.IsEmpty();
  }
}

// static
std::optional<VideoFrameLayout>
VideoFrame::CreateFullySpecifiedLayoutWithStrides(VideoPixelFormat format,
                                                  const gfx::Size& coded_size) {
  const gfx::Size new_coded_size = DetermineAlignedSize(format, coded_size);
  auto layout = VideoFrameLayout::CreateWithStrides(
      format, new_coded_size, ComputeStrides(format, new_coded_size));
  if (!layout) {
    return {};
  }
  // This whole method would be in `VideoFrameLayout::CreateWithStrides()`
  // instead of here, except that we know how to calculate the plane sizes.
  // This should be refactored.
  auto plane_sizes = CalculatePlaneSize(*layout);
  // Fill in the offsets as well, since WrapExternalDataWithLayout() uses them
  // to figure out where the data is.
  size_t offset = 0u;
  const size_t num_planes = plane_sizes.size();
  std::vector<ColorPlaneLayout> new_planes;
  new_planes.reserve(num_planes);
  for (size_t plane = 0; plane < plane_sizes.size(); plane++) {
    new_planes.emplace_back(layout->planes()[plane].stride, offset,
                            plane_sizes[plane]);
    offset += plane_sizes[plane];
  }
  return VideoFrameLayout::CreateWithPlanes(format, new_coded_size,
                                            std::move(new_planes));
}

// static
scoped_refptr<VideoFrame> VideoFrame::CreateFrameInternal(
    VideoPixelFormat format,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp,
    bool zero_initialize_memory) {
  // Since we're creating a new frame (and allocating memory for it
  // ourselves), we can pad the requested |coded_size| if necessary if the
  // request does not line up on sample boundaries. See discussion at
  // http://crrev.com/1240833003
  const gfx::Size new_coded_size = DetermineAlignedSize(format, coded_size);
  auto layout = VideoFrameLayout::CreateWithStrides(
      format, new_coded_size, ComputeStrides(format, new_coded_size));
  if (!layout) {
    DLOG(ERROR) << "Invalid layout.";
    return nullptr;
  }

  return CreateFrameWithLayout(*layout, visible_rect, natural_size, timestamp,
                               zero_initialize_memory);
}

scoped_refptr<VideoFrame> VideoFrame::CreateFrameWithLayout(
    const VideoFrameLayout& layout,
    const gfx::Rect& visible_rect,
    const gfx::Size& natural_size,
    base::TimeDelta timestamp,
    bool zero_initialize_memory) {
  const StorageType storage = STORAGE_OWNED_MEMORY;
  if (!IsValidConfig(layout.format(), storage, layout.coded_size(),
                     visible_rect, natural_size)) {
    DLOG(ERROR) << __func__ << " Invalid config."
                << ConfigToString(layout.format(), storage, layout.coded_size(),
                                  visible_rect, natural_size);
    return nullptr;
  }

  auto frame = base::MakeRefCounted<VideoFrame>(
      base::PassKey<VideoFrame>(), std::move(layout), storage, visible_rect,
      natural_size, timestamp);
  return frame->AllocateMemory(zero_initialize_memory) ? frame : nullptr;
}

// static
gfx::Size VideoFrame::CommonAlignment(VideoPixelFormat format) {
  int max_sample_width = 0;
  int max_sample_height = 0;
  for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
    const gfx::Size sample_size = SampleSize(format, plane);
    max_sample_width = std::max(max_sample_width, sample_size.width());
    max_sample_height = std::max(max_sample_height, sample_size.height());
  }
  return gfx::Size(max_sample_width, max_sample_height);
}

bool VideoFrame::AllocateMemory(bool zero_initialize_memory) {
  DCHECK_EQ(storage_type_, STORAGE_OWNED_MEMORY);
  static_assert(0 == Plane::kY, "y plane data must be index 0");

  std::vector<size_t> plane_size = CalculatePlaneSize();
  const size_t buffer_size =
      std::accumulate(plane_size.begin(), plane_size.end(), 0u);
  const size_t allocation_size =
      buffer_size + (layout_.buffer_addr_align() - 1);

  uint8_t* data = nullptr;
  if (zero_initialize_memory) {
    if (!base::UncheckedCalloc(1, allocation_size,
                               reinterpret_cast<void**>(&data)) ||
        !data) {
      return false;
    }
  } else {
    if (!base::UncheckedMalloc(allocation_size,
                               reinterpret_cast<void**>(&data)) ||
        !data) {
      return false;
    }
  }
  private_data_.reset(data);
  // SAFETY: We've just allocated a region of `allocation_size` at `data`.
  auto allocated_region = UNSAFE_BUFFERS(base::span(data, allocation_size));
  const auto alignment_offset = base::checked_cast<size_t>(
      base::bits::AlignUp(data, layout_.buffer_addr_align()) - data);
  allocated_region = allocated_region.subspan(alignment_offset, buffer_size);

  // Note that if layout.buffer_sizes is specified, color planes' layout is
  // the same as buffers'. See CalculatePlaneSize() for detail.
  for (size_t plane = 0, offset = 0; plane < NumPlanes(format()); ++plane) {
    data_[plane] = allocated_region.subspan(offset, plane_size[plane]);
    offset += plane_size[plane];
  }

  return true;
}

bool VideoFrame::IsValidSharedMemoryFrame() const {
  if (storage_type_ == STORAGE_SHMEM)
    return shm_region_ && shm_region_->IsValid();
  return false;
}

// static
std::vector<size_t> VideoFrame::CalculatePlaneSize(
    const VideoFrameLayout& layout) {
  std::vector<size_t> plane_size(layout.planes().size());
  DCHECK_EQ(plane_size.size(), NumPlanes(layout.format()));
  for (size_t i = 0; i < plane_size.size(); ++i) {
    plane_size[i] = layout.planes()[i].size;
  }
  return plane_size;
}

std::vector<size_t> VideoFrame::CalculatePlaneSize() const {
  return CalculatePlaneSize(layout_);
}

}  // namespace media
