| // 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_util.h" |
| |
| #include <array> |
| #include <cmath> |
| |
| #include "base/bits.h" |
| #include "base/check_op.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/span.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/numerics/safe_math.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "components/viz/common/resources/shared_image_format.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/raster_interface.h" |
| #include "media/base/limits.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_frame_pool.h" |
| #include "media/base/video_types.h" |
| #include "media/base/wait_and_replace_sync_token_client.h" |
| #include "third_party/libyuv/include/libyuv.h" |
| #include "third_party/skia/include/core/SkAlphaType.h" |
| #include "third_party/skia/include/core/SkColorType.h" |
| #include "third_party/skia/include/core/SkImage.h" |
| #include "third_party/skia/include/core/SkImageInfo.h" |
| #include "third_party/skia/include/core/SkPixmap.h" |
| #include "third_party/skia/include/core/SkRefCnt.h" |
| #include "third_party/skia/include/core/SkYUVAPixmaps.h" |
| #include "third_party/skia/include/gpu/GpuTypes.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Helper to apply padding to the region outside visible rect up to the coded |
| // size with the repeated last column / row of the visible rect. |
| void FillRegionOutsideVisibleRect(uint8_t* data, |
| size_t stride, |
| const gfx::Size& coded_size, |
| const gfx::Size& visible_size) { |
| if (visible_size.IsEmpty()) { |
| if (!coded_size.IsEmpty()) |
| UNSAFE_TODO(memset(data, 0, coded_size.height() * stride)); |
| return; |
| } |
| |
| const int coded_width = coded_size.width(); |
| if (visible_size.width() < coded_width) { |
| const int pad_length = coded_width - visible_size.width(); |
| uint8_t* dst = UNSAFE_TODO(data + visible_size.width()); |
| for (int i = 0; i < visible_size.height(); |
| ++i, UNSAFE_TODO(dst += stride)) { |
| UNSAFE_TODO(memset(dst, *(dst - 1), pad_length)); |
| } |
| } |
| |
| if (visible_size.height() < coded_size.height()) { |
| uint8_t* dst = UNSAFE_TODO(data + visible_size.height() * stride); |
| uint8_t* src = UNSAFE_TODO(dst - stride); |
| for (int i = visible_size.height(); i < coded_size.height(); |
| ++i, UNSAFE_TODO(dst += stride)) { |
| UNSAFE_TODO(memcpy(dst, src, coded_width)); |
| } |
| } |
| } |
| |
| VideoPixelFormat ReadbackFormat(const VideoFrame& frame) { |
| // The |frame|.BitDepth() restriction is to avoid treating a P010LE frame as a |
| // low-bit depth frame. |
| bool si_prefers_external_sampler = |
| frame.HasSharedImage() && |
| frame.shared_image()->format().PrefersExternalSampler(); |
| if (si_prefers_external_sampler && frame.BitDepth() == 8u) { |
| return PIXEL_FORMAT_XRGB; |
| } |
| |
| switch (frame.format()) { |
| case PIXEL_FORMAT_I420: |
| case PIXEL_FORMAT_I420A: |
| case PIXEL_FORMAT_I422: |
| case PIXEL_FORMAT_I444: |
| case PIXEL_FORMAT_ARGB: |
| case PIXEL_FORMAT_XRGB: |
| case PIXEL_FORMAT_ABGR: |
| case PIXEL_FORMAT_XBGR: |
| case PIXEL_FORMAT_NV12: |
| case PIXEL_FORMAT_NV16: |
| case PIXEL_FORMAT_NV24: |
| case PIXEL_FORMAT_NV12A: |
| return frame.format(); |
| default: |
| // Currently unsupported. |
| return PIXEL_FORMAT_UNKNOWN; |
| } |
| } |
| |
| void LetterboxPlane(const gfx::Rect& view_area_in_bytes, |
| base::span<uint8_t> plane_data, |
| int rows, |
| int row_bytes, |
| int stride, |
| int bytes_per_element, |
| uint8_t fill_byte) { |
| if (view_area_in_bytes.IsEmpty()) { |
| libyuv::SetPlane(plane_data.data(), stride, row_bytes, rows, fill_byte); |
| return; |
| } |
| |
| if (view_area_in_bytes.y() > 0) { |
| libyuv::SetPlane(plane_data.data(), stride, row_bytes, |
| view_area_in_bytes.y(), fill_byte); |
| plane_data = plane_data.subspan( |
| static_cast<size_t>(stride * view_area_in_bytes.y())); |
| } |
| |
| if (view_area_in_bytes.width() < row_bytes) { |
| if (view_area_in_bytes.x() > 0) { |
| libyuv::SetPlane(plane_data.data(), stride, view_area_in_bytes.x(), |
| view_area_in_bytes.height(), fill_byte); |
| } |
| if (view_area_in_bytes.right() < row_bytes) { |
| libyuv::SetPlane( |
| plane_data.subspan(static_cast<size_t>(view_area_in_bytes.right())) |
| .data(), |
| stride, row_bytes - view_area_in_bytes.right(), |
| view_area_in_bytes.height(), fill_byte); |
| } |
| } |
| |
| plane_data = plane_data.subspan( |
| static_cast<size_t>(stride * view_area_in_bytes.height())); |
| |
| if (view_area_in_bytes.bottom() < rows) { |
| libyuv::SetPlane(plane_data.data(), stride, row_bytes, |
| rows - view_area_in_bytes.bottom(), fill_byte); |
| } |
| } |
| |
| void LetterboxPlane(VideoFrame* frame, |
| int plane, |
| base::span<uint8_t> plane_data, |
| const gfx::Rect& view_area_in_pixels, |
| uint8_t fill_byte) { |
| const int rows = frame->rows(plane); |
| const int row_bytes = frame->row_bytes(plane); |
| const int stride = frame->stride(plane); |
| const int bytes_per_element = |
| VideoFrame::BytesPerElement(frame->format(), plane); |
| |
| gfx::Rect view_area_in_bytes(view_area_in_pixels.x() * bytes_per_element, |
| view_area_in_pixels.y(), |
| view_area_in_pixels.width() * bytes_per_element, |
| view_area_in_pixels.height()); |
| |
| CHECK_GE(stride, row_bytes); |
| CHECK_GE(view_area_in_bytes.x(), 0); |
| CHECK_GE(view_area_in_bytes.y(), 0); |
| CHECK_LE(view_area_in_bytes.right(), row_bytes); |
| CHECK_LE(view_area_in_bytes.bottom(), rows); |
| |
| LetterboxPlane(view_area_in_bytes, plane_data, rows, row_bytes, stride, |
| bytes_per_element, fill_byte); |
| } |
| |
| // Helper for `LetterboxVideoFrame()`, assumes that if |frame| is GMB-backed, |
| // the GpuMemoryBuffer is already mapped (via a call to `Map()`). |
| void LetterboxPlane(VideoFrame* frame, |
| VideoFrame::ScopedMapping* scoped_mapping, |
| int plane, |
| const gfx::Rect& view_area_in_pixels, |
| uint8_t fill_byte) { |
| base::span<uint8_t> plane_data; |
| if (frame->IsMappable()) { |
| plane_data = frame->writable_span(plane); |
| } else if (scoped_mapping) { |
| plane_data = scoped_mapping->GetMemoryAsSpan(plane); |
| } |
| CHECK(!plane_data.empty()); |
| |
| LetterboxPlane(frame, plane, plane_data, view_area_in_pixels, fill_byte); |
| } |
| |
| void ProcessAsyncMappingResult( |
| scoped_refptr<VideoFrame> video_frame, |
| base::OnceCallback<void(scoped_refptr<VideoFrame>)> result_cb, |
| std::unique_ptr<VideoFrame::ScopedMapping> scoped_mapping) { |
| CHECK(video_frame); |
| if (!scoped_mapping) { |
| std::move(result_cb).Run(nullptr); |
| return; |
| } |
| |
| const size_t num_planes = VideoFrame::NumPlanes(video_frame->format()); |
| std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> planes = {}; |
| for (size_t i = 0; i < num_planes; i++) { |
| planes[i] = scoped_mapping->GetMemoryAsSpan(i); |
| } |
| |
| auto mapped_frame = VideoFrame::WrapExternalYuvDataWithLayout( |
| video_frame->layout(), video_frame->visible_rect(), |
| video_frame->natural_size(), planes[0], planes[1], planes[2], |
| video_frame->timestamp()); |
| |
| if (!mapped_frame) { |
| std::move(result_cb).Run(nullptr); |
| return; |
| } |
| |
| mapped_frame->set_color_space(video_frame->ColorSpace()); |
| mapped_frame->metadata().MergeMetadataFrom(video_frame->metadata()); |
| |
| // Pass |video_frame| so that it outlives |mapped_frame| and the mapped buffer |
| // is unmapped on destruction. |
| mapped_frame->AddDestructionObserver(base::BindOnce( |
| [](scoped_refptr<VideoFrame> frame, |
| std::unique_ptr<VideoFrame::ScopedMapping> scoped_mapping) { |
| CHECK(scoped_mapping); |
| // The VideoFrame::ScopedMapping must be destroyed before the |
| // FrameResource that produced it in order to avoid dangling pointers. |
| scoped_mapping.reset(); |
| }, |
| std::move(video_frame), std::move(scoped_mapping))); |
| std::move(result_cb).Run(std::move(mapped_frame)); |
| } |
| |
| } // namespace |
| |
| void FillYUV(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v) { |
| libyuv::I420Rect(frame->writable_data(VideoFrame::Plane::kY), |
| frame->stride(VideoFrame::Plane::kY), |
| frame->writable_data(VideoFrame::Plane::kU), |
| frame->stride(VideoFrame::Plane::kU), |
| frame->writable_data(VideoFrame::Plane::kV), |
| frame->stride(VideoFrame::Plane::kV), 0, 0, |
| frame->coded_size().width(), frame->coded_size().height(), y, |
| u, v); |
| } |
| |
| void FillYUVA(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v, uint8_t a) { |
| // Fill Y, U and V planes. |
| FillYUV(frame, y, u, v); |
| |
| // Fill the A plane. |
| libyuv::SetPlane(frame->writable_data(VideoFrame::Plane::kA), |
| frame->stride(VideoFrame::Plane::kA), |
| frame->row_bytes(VideoFrame::Plane::kA), |
| frame->rows(VideoFrame::Plane::kA), a); |
| } |
| |
| void LetterboxVideoFrame(VideoFrame* frame, const gfx::Rect& view_area) { |
| std::unique_ptr<VideoFrame::ScopedMapping> scoped_mapping; |
| if (!frame->IsMappable() && |
| frame->storage_type() == media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER) { |
| scoped_mapping = frame->MapGMBOrSharedImage(); |
| CHECK(scoped_mapping); |
| } |
| |
| switch (frame->format()) { |
| case PIXEL_FORMAT_ARGB: |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kARGB, |
| view_area, 0x00); |
| break; |
| case PIXEL_FORMAT_YV12: |
| case PIXEL_FORMAT_I420: { |
| DCHECK(!(view_area.x() & 1)); |
| DCHECK(!(view_area.y() & 1)); |
| DCHECK(!(view_area.width() & 1)); |
| DCHECK(!(view_area.height() & 1)); |
| |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kY, |
| view_area, 0x00); |
| gfx::Rect half_view_area(view_area.x() / 2, view_area.y() / 2, |
| view_area.width() / 2, view_area.height() / 2); |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kU, |
| half_view_area, 0x80); |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kV, |
| half_view_area, 0x80); |
| break; |
| } |
| case PIXEL_FORMAT_NV12: { |
| DCHECK(!(view_area.x() & 1)); |
| DCHECK(!(view_area.y() & 1)); |
| DCHECK(!(view_area.width() & 1)); |
| DCHECK(!(view_area.height() & 1)); |
| |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kY, |
| view_area, 0x00); |
| gfx::Rect half_view_area(view_area.x() / 2, view_area.y() / 2, |
| view_area.width() / 2, view_area.height() / 2); |
| |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kUV, |
| half_view_area, 0x80); |
| break; |
| } |
| case PIXEL_FORMAT_NV12A: { |
| DCHECK(!(view_area.x() & 1)); |
| DCHECK(!(view_area.y() & 1)); |
| DCHECK(!(view_area.width() & 1)); |
| DCHECK(!(view_area.height() & 1)); |
| |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kY, |
| view_area, 0x00); |
| gfx::Rect half_view_area(view_area.x() / 2, view_area.y() / 2, |
| view_area.width() / 2, view_area.height() / 2); |
| LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kUV, |
| half_view_area, 0x80); |
| LetterboxPlane(frame, scoped_mapping.get(), |
| VideoFrame::Plane::kATriPlanar, view_area, 0x00); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void RotatePlaneByPixels(const uint8_t* src, |
| uint8_t* dest, |
| int width, |
| int height, |
| int rotation, // Clockwise. |
| bool flip_vert, |
| bool flip_horiz) { |
| DCHECK((width > 0) && (height > 0) && |
| ((width & 1) == 0) && ((height & 1) == 0) && |
| (rotation >= 0) && (rotation < 360) && (rotation % 90 == 0)); |
| |
| // Consolidate cases. Only 0 and 90 are left. |
| if (rotation == 180 || rotation == 270) { |
| rotation -= 180; |
| flip_vert = !flip_vert; |
| flip_horiz = !flip_horiz; |
| } |
| |
| int num_rows = height; |
| int num_cols = width; |
| int src_stride = width; |
| // During pixel copying, the corresponding incremental of dest pointer |
| // when src pointer moves to next row. |
| int dest_row_step = width; |
| // During pixel copying, the corresponding incremental of dest pointer |
| // when src pointer moves to next column. |
| int dest_col_step = 1; |
| |
| if (rotation == 0) { |
| if (flip_horiz) { |
| // Use pixel copying. |
| dest_col_step = -1; |
| if (flip_vert) { |
| // Rotation 180. |
| dest_row_step = -width; |
| UNSAFE_TODO(dest += height * width - 1); |
| } else { |
| UNSAFE_TODO(dest += width - 1); |
| } |
| } else { |
| if (flip_vert) { |
| // Fast copy by rows. |
| UNSAFE_TODO(dest += width * (height - 1)); |
| for (int row = 0; row < height; ++row) { |
| UNSAFE_TODO(memcpy(dest, src, width)); |
| UNSAFE_TODO(src += width); |
| UNSAFE_TODO(dest -= width); |
| } |
| } else { |
| UNSAFE_TODO(memcpy(dest, src, width * height)); |
| } |
| return; |
| } |
| } else if (rotation == 90) { |
| int offset; |
| if (width > height) { |
| offset = (width - height) / 2; |
| UNSAFE_TODO(src += offset); |
| num_rows = num_cols = height; |
| } else { |
| offset = (height - width) / 2; |
| UNSAFE_TODO(src += width * offset); |
| num_rows = num_cols = width; |
| } |
| |
| dest_col_step = (flip_vert ? -width : width); |
| dest_row_step = (flip_horiz ? 1 : -1); |
| if (flip_horiz) { |
| if (flip_vert) { |
| UNSAFE_TODO(dest += (width > height ? width * (height - 1) + offset |
| : width * (height - offset - 1))); |
| } else { |
| UNSAFE_TODO(dest += (width > height ? offset : width * offset)); |
| } |
| } else { |
| if (flip_vert) { |
| UNSAFE_TODO(dest += (width > height ? width * height - offset - 1 |
| : width * (height - offset) - 1)); |
| } else { |
| UNSAFE_TODO(dest += (width > height ? width - offset - 1 |
| : width * (offset + 1) - 1)); |
| } |
| } |
| } else { |
| NOTREACHED(); |
| } |
| |
| // Copy pixels. |
| for (int row = 0; row < num_rows; ++row) { |
| const uint8_t* src_ptr = src; |
| uint8_t* dest_ptr = dest; |
| for (int col = 0; col < num_cols; ++col) { |
| *dest_ptr = *UNSAFE_TODO(src_ptr++); |
| UNSAFE_TODO(dest_ptr += dest_col_step); |
| } |
| UNSAFE_TODO(src += src_stride); |
| UNSAFE_TODO(dest += dest_row_step); |
| } |
| } |
| |
| // Helper function to return |a| divided by |b|, rounded to the nearest integer. |
| static int RoundedDivision(int64_t a, int b) { |
| DCHECK_GE(a, 0); |
| DCHECK_GT(b, 0); |
| base::CheckedNumeric<uint64_t> result(a); |
| result += b / 2; |
| result /= b; |
| return base::ValueOrDieForType<int>(result); |
| } |
| |
| // Common logic for the letterboxing and scale-within/scale-encompassing |
| // functions. Scales |size| to either fit within or encompass |target|, |
| // depending on whether |fit_within_target| is true. |
| static gfx::Size ScaleSizeToTarget(const gfx::Size& size, |
| const gfx::Size& target, |
| bool fit_within_target) { |
| if (size.IsEmpty()) |
| return gfx::Size(); // Corner case: Aspect ratio is undefined. |
| |
| const int64_t x = static_cast<int64_t>(size.width()) * target.height(); |
| const int64_t y = static_cast<int64_t>(size.height()) * target.width(); |
| const bool use_target_width = fit_within_target ? (y < x) : (x < y); |
| return use_target_width ? |
| gfx::Size(target.width(), RoundedDivision(y, size.width())) : |
| gfx::Size(RoundedDivision(x, size.height()), target.height()); |
| } |
| |
| gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds, |
| const gfx::Size& content) { |
| // If |content| has an undefined aspect ratio, let's not try to divide by |
| // zero. |
| if (content.IsEmpty()) |
| return gfx::Rect(); |
| |
| gfx::Rect result = bounds; |
| result.ClampToCenteredSize(ScaleSizeToTarget(content, bounds.size(), true)); |
| return result; |
| } |
| |
| gfx::Rect ComputeLetterboxRegionForI420(const gfx::Rect& bounds, |
| const gfx::Size& content) { |
| DCHECK_EQ(bounds.x() % 2, 0); |
| DCHECK_EQ(bounds.y() % 2, 0); |
| DCHECK_EQ(bounds.width() % 2, 0); |
| DCHECK_EQ(bounds.height() % 2, 0); |
| |
| gfx::Rect result = ComputeLetterboxRegion(bounds, content); |
| |
| if (result.x() & 1) { |
| // This is always legal since bounds.x() was even and result.x() must always |
| // be greater or equal to bounds.x(). |
| result.set_x(result.x() - 1); |
| |
| // The result.x() was nudged to the left, so if the width is odd, it should |
| // be perfectly legal to nudge it up by one to make it even. |
| if (result.width() & 1) |
| result.set_width(result.width() + 1); |
| } else /* if (result.x() is even) */ { |
| if (result.width() & 1) |
| result.set_width(result.width() - 1); |
| } |
| |
| if (result.y() & 1) { |
| // These operations are legal for the same reasons mentioned above for |
| // result.x(). |
| result.set_y(result.y() - 1); |
| if (result.height() & 1) |
| result.set_height(result.height() + 1); |
| } else /* if (result.y() is even) */ { |
| if (result.height() & 1) |
| result.set_height(result.height() - 1); |
| } |
| |
| return result; |
| } |
| |
| gfx::Rect MinimallyShrinkRectForI420(const gfx::Rect& rect) { |
| constexpr int kMinDimension = -1 * limits::kMaxDimension; |
| DCHECK(gfx::Rect(kMinDimension, kMinDimension, limits::kMaxDimension * 2, |
| limits::kMaxDimension * 2) |
| .Contains(rect)); |
| |
| const auto positive_mod = [](int a, int b) { return (a % b + b) % b; }; |
| |
| const int left = rect.x() + positive_mod(rect.x(), 2); |
| const int top = rect.y() + positive_mod(rect.y(), 2); |
| const int right = rect.right() - (rect.right() % 2); |
| const int bottom = rect.bottom() - (rect.bottom() % 2); |
| |
| return gfx::Rect(left, top, std::max(0, right - left), |
| std::max(0, bottom - top)); |
| } |
| |
| gfx::Size ScaleSizeToFitWithinTarget(const gfx::Size& size, |
| const gfx::Size& target) { |
| return ScaleSizeToTarget(size, target, true); |
| } |
| |
| gfx::Size ScaleSizeToEncompassTarget(const gfx::Size& size, |
| const gfx::Size& target) { |
| return ScaleSizeToTarget(size, target, false); |
| } |
| |
| gfx::Rect CropSizeForScalingToTarget(const gfx::Size& size, |
| const gfx::Size& target, |
| size_t alignment) { |
| DCHECK_GT(alignment, 0u); |
| if (size.IsEmpty() || target.IsEmpty()) |
| return gfx::Rect(); |
| |
| gfx::Rect crop(ScaleSizeToFitWithinTarget(target, size)); |
| crop.set_width(base::checked_cast<int>(base::bits::AlignDown( |
| base::checked_cast<size_t>(crop.width()), alignment))); |
| crop.set_height(base::checked_cast<int>(base::bits::AlignDown( |
| base::checked_cast<size_t>(crop.height()), alignment))); |
| crop.set_x(base::checked_cast<int>(base::bits::AlignDown( |
| base::checked_cast<size_t>((size.width() - crop.width()) / 2), |
| alignment))); |
| crop.set_y(base::checked_cast<int>(base::bits::AlignDown( |
| base::checked_cast<size_t>((size.height() - crop.height()) / 2), |
| alignment))); |
| DCHECK(gfx::Rect(size).Contains(crop)); |
| return crop; |
| } |
| |
| gfx::Size GetRectSizeFromOrigin(const gfx::Rect& rect) { |
| return gfx::Size(rect.right(), rect.bottom()); |
| } |
| |
| gfx::Size PadToMatchAspectRatio(const gfx::Size& size, |
| const gfx::Size& target) { |
| if (target.IsEmpty()) |
| return gfx::Size(); // Aspect ratio is undefined. |
| |
| const int64_t x = static_cast<int64_t>(size.width()) * target.height(); |
| const int64_t y = static_cast<int64_t>(size.height()) * target.width(); |
| if (x < y) |
| return gfx::Size(RoundedDivision(y, target.height()), size.height()); |
| return gfx::Size(size.width(), RoundedDivision(x, target.width())); |
| } |
| |
| scoped_refptr<VideoFrame> ConvertToMemoryMappedFrame( |
| scoped_refptr<VideoFrame> video_frame) { |
| CHECK(video_frame); |
| CHECK(video_frame->HasMappableGpuBuffer()); |
| |
| auto scoped_mapping = video_frame->MapGMBOrSharedImage(); |
| if (!scoped_mapping) { |
| return nullptr; |
| } |
| |
| const size_t num_planes = VideoFrame::NumPlanes(video_frame->format()); |
| std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> planes = {}; |
| for (size_t i = 0; i < num_planes; i++) |
| planes[i] = scoped_mapping->GetMemoryAsSpan(i); |
| |
| auto mapped_frame = VideoFrame::WrapExternalYuvDataWithLayout( |
| video_frame->layout(), video_frame->visible_rect(), |
| video_frame->natural_size(), planes[0], planes[1], planes[2], |
| video_frame->timestamp()); |
| |
| if (!mapped_frame) { |
| return nullptr; |
| } |
| |
| mapped_frame->set_color_space(video_frame->ColorSpace()); |
| mapped_frame->metadata().MergeMetadataFrom(video_frame->metadata()); |
| |
| // Pass |video_frame| so that it outlives |mapped_frame| and the mapped buffer |
| // is unmapped on destruction. |
| mapped_frame->AddDestructionObserver(base::BindOnce( |
| [](scoped_refptr<VideoFrame> frame, |
| std::unique_ptr<VideoFrame::ScopedMapping> scoped_mapping) { |
| CHECK(scoped_mapping); |
| // The VideoFrame::ScopedMapping must be destroyed before the |
| // FrameResource that produced it in order to avoid dangling pointers. |
| scoped_mapping.reset(); |
| }, |
| std::move(video_frame), std::move(scoped_mapping))); |
| return mapped_frame; |
| } |
| |
| void ConvertToMemoryMappedFrameAsync( |
| scoped_refptr<VideoFrame> video_frame, |
| base::OnceCallback<void(scoped_refptr<VideoFrame>)> result_cb) { |
| CHECK(video_frame); |
| CHECK(video_frame->HasMappableGpuBuffer()); |
| |
| video_frame->MapGMBOrSharedImageAsync(base::BindOnce( |
| &ProcessAsyncMappingResult, video_frame, std::move(result_cb))); |
| } |
| |
| scoped_refptr<VideoFrame> WrapAsI420VideoFrame( |
| scoped_refptr<VideoFrame> frame) { |
| DCHECK_EQ(VideoFrame::STORAGE_OWNED_MEMORY, frame->storage_type()); |
| DCHECK_EQ(PIXEL_FORMAT_I420A, frame->format()); |
| |
| scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame( |
| frame, PIXEL_FORMAT_I420, frame->visible_rect(), frame->natural_size()); |
| return wrapped_frame; |
| } |
| |
| bool I420CopyWithPadding(const VideoFrame& src_frame, VideoFrame* dst_frame) { |
| if (!dst_frame || !dst_frame->IsMappable()) |
| return false; |
| |
| DCHECK_GE(dst_frame->coded_size().width(), src_frame.visible_rect().width()); |
| DCHECK_GE(dst_frame->coded_size().height(), |
| src_frame.visible_rect().height()); |
| DCHECK(dst_frame->visible_rect().origin().IsOrigin()); |
| |
| if (libyuv::I420Copy(src_frame.visible_data(VideoFrame::Plane::kY), |
| src_frame.stride(VideoFrame::Plane::kY), |
| src_frame.visible_data(VideoFrame::Plane::kU), |
| src_frame.stride(VideoFrame::Plane::kU), |
| src_frame.visible_data(VideoFrame::Plane::kV), |
| src_frame.stride(VideoFrame::Plane::kV), |
| dst_frame->writable_data(VideoFrame::Plane::kY), |
| dst_frame->stride(VideoFrame::Plane::kY), |
| dst_frame->writable_data(VideoFrame::Plane::kU), |
| dst_frame->stride(VideoFrame::Plane::kU), |
| dst_frame->writable_data(VideoFrame::Plane::kV), |
| dst_frame->stride(VideoFrame::Plane::kV), |
| src_frame.visible_rect().width(), |
| src_frame.visible_rect().height())) { |
| return false; |
| } |
| |
| // Padding the region outside the visible rect with the repeated last |
| // column / row of the visible rect. This can improve the coding efficiency. |
| FillRegionOutsideVisibleRect(dst_frame->writable_data(VideoFrame::Plane::kY), |
| dst_frame->stride(VideoFrame::Plane::kY), |
| dst_frame->coded_size(), |
| src_frame.visible_rect().size()); |
| FillRegionOutsideVisibleRect( |
| dst_frame->writable_data(VideoFrame::Plane::kU), |
| dst_frame->stride(VideoFrame::Plane::kU), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kU, |
| dst_frame->coded_size()), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kU, |
| src_frame.visible_rect().size())); |
| FillRegionOutsideVisibleRect( |
| dst_frame->writable_data(VideoFrame::Plane::kV), |
| dst_frame->stride(VideoFrame::Plane::kV), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kV, |
| dst_frame->coded_size()), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kV, |
| src_frame.visible_rect().size())); |
| |
| return true; |
| } |
| |
| scoped_refptr<VideoFrame> ReadbackTextureBackedFrameToMemorySync( |
| VideoFrame& txt_frame, |
| gpu::raster::RasterInterface* ri, |
| VideoFramePool* pool) { |
| DCHECK(ri); |
| |
| TRACE_EVENT1("media", "ReadbackTextureBackedFrameToMemorySync", "timestamp", |
| txt_frame.timestamp()); |
| VideoPixelFormat format = ReadbackFormat(txt_frame); |
| if (format == PIXEL_FORMAT_UNKNOWN) { |
| DLOG(ERROR) << "Readback is not possible for this frame: " |
| << txt_frame.AsHumanReadableString(); |
| return nullptr; |
| } |
| |
| scoped_refptr<VideoFrame> result = |
| pool ? pool->CreateFrame(format, txt_frame.coded_size(), |
| txt_frame.visible_rect(), |
| txt_frame.natural_size(), txt_frame.timestamp()) |
| : VideoFrame::CreateFrame( |
| format, txt_frame.coded_size(), txt_frame.visible_rect(), |
| txt_frame.natural_size(), txt_frame.timestamp()); |
| result->set_color_space(txt_frame.ColorSpace()); |
| result->metadata().MergeMetadataFrom(txt_frame.metadata()); |
| result->metadata().ClearTextureFrameMetadata(); |
| |
| // NOTE: Iterating over the number of planes of the readback format (rather |
| // than `txt_frame`) ensures that frames with external |
| // sampling are correctly sampled as a single opaque texture, as |
| // ReadbackFormat() returns RGB for such frames. |
| size_t planes = VideoFrame::NumPlanes(format); |
| for (size_t plane = 0; plane < planes; plane++) { |
| gfx::Rect src_rect(0, 0, txt_frame.columns(plane), txt_frame.rows(plane)); |
| if (!ReadbackTexturePlaneToMemorySync(txt_frame, plane, src_rect, |
| result->writable_data(plane), |
| result->stride(plane), ri)) { |
| return nullptr; |
| } |
| } |
| return result; |
| } |
| |
| bool ReadbackTexturePlaneToMemorySync(VideoFrame& src_frame, |
| size_t src_plane, |
| gfx::Rect& src_rect, |
| uint8_t* dest_pixels, |
| size_t dest_stride, |
| gpu::raster::RasterInterface* ri) { |
| DCHECK(ri); |
| VideoPixelFormat format = ReadbackFormat(src_frame); |
| if (format == PIXEL_FORMAT_UNKNOWN) { |
| DLOG(ERROR) << "Readback is not possible for this frame: " |
| << src_frame.AsHumanReadableString(); |
| return false; |
| } |
| |
| bool has_alpha = !IsOpaque(format); |
| auto sk_color_type = SkColorTypeForPlane(format, src_plane); |
| auto sk_alpha_type = has_alpha ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType; |
| |
| auto info = SkImageInfo::Make(src_rect.width(), src_rect.height(), |
| sk_color_type, sk_alpha_type); |
| |
| // Perform readback passing the appropriate `src_plane` for the mailbox. |
| auto mailbox = src_frame.shared_image()->mailbox(); |
| auto sync_token = src_frame.acquire_sync_token(); |
| std::unique_ptr<gpu::RasterScopedAccess> ri_access = |
| src_frame.shared_image()->BeginRasterAccess(ri, sync_token, |
| /*readonly=*/true); |
| bool readback_result = |
| ri->ReadbackImagePixels(mailbox, info, dest_stride, src_rect.x(), |
| src_rect.y(), src_plane, dest_pixels); |
| |
| bool result = readback_result && |
| ri->GetGraphicsResetStatusKHR() == GL_NO_ERROR && |
| ri->GetError() == GL_NO_ERROR; |
| if (result) { |
| WaitAndReplaceSyncTokenClient client(ri, std::move(ri_access)); |
| src_frame.UpdateReleaseSyncToken(&client); |
| } else { |
| gpu::RasterScopedAccess::EndAccess(std::move(ri_access)); |
| } |
| |
| return result; |
| } |
| |
| // Media pixel format enums have names opposite to their byte order. |
| // That's why PIXEL_FORMAT_ABGR corresponds to kRGBA_8888_SkColorType |
| // and so on. |
| MEDIA_EXPORT SkColorType SkColorTypeForPlane(VideoPixelFormat format, |
| size_t plane) { |
| switch (format) { |
| case PIXEL_FORMAT_I420: |
| case PIXEL_FORMAT_I420A: |
| case PIXEL_FORMAT_I422: |
| case PIXEL_FORMAT_I444: |
| // kGray_8_SkColorType would make more sense but doesn't work on Windows. |
| return kAlpha_8_SkColorType; |
| case PIXEL_FORMAT_NV12: |
| case PIXEL_FORMAT_NV16: |
| case PIXEL_FORMAT_NV24: |
| return plane == VideoFrame::Plane::kY ? kAlpha_8_SkColorType |
| : kR8G8_unorm_SkColorType; |
| case PIXEL_FORMAT_NV12A: |
| return plane == VideoFrame::Plane::kY || |
| plane == VideoFrame::Plane::kATriPlanar |
| ? kAlpha_8_SkColorType |
| : kR8G8_unorm_SkColorType; |
| case PIXEL_FORMAT_P010LE: |
| case PIXEL_FORMAT_P210LE: |
| case PIXEL_FORMAT_P410LE: |
| return plane == VideoFrame::Plane::kY ? kA16_unorm_SkColorType |
| : kR16G16_unorm_SkColorType; |
| case PIXEL_FORMAT_XBGR: |
| case PIXEL_FORMAT_ABGR: |
| return kRGBA_8888_SkColorType; |
| case PIXEL_FORMAT_XRGB: |
| case PIXEL_FORMAT_ARGB: |
| return kBGRA_8888_SkColorType; |
| case PIXEL_FORMAT_RGBAF16: |
| return kRGBA_F16_SkColorType; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| MEDIA_EXPORT VideoPixelFormat |
| VideoPixelFormatFromSkColorType(SkColorType sk_color_type, bool is_opaque) { |
| switch (sk_color_type) { |
| case kRGBA_8888_SkColorType: |
| return is_opaque ? PIXEL_FORMAT_XBGR : PIXEL_FORMAT_ABGR; |
| case kBGRA_8888_SkColorType: |
| return is_opaque ? PIXEL_FORMAT_XRGB : PIXEL_FORMAT_ARGB; |
| case kRGBA_F16_SkColorType: |
| return PIXEL_FORMAT_RGBAF16; |
| default: |
| return PIXEL_FORMAT_UNKNOWN; |
| } |
| } |
| |
| scoped_refptr<VideoFrame> CreateFromSkImage(sk_sp<SkImage> sk_image, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| base::TimeDelta timestamp, |
| bool force_opaque) { |
| DCHECK(!sk_image->isTextureBacked()); |
| |
| // A given SkImage may not exist until it's rasterized. |
| if (sk_image->isLazyGenerated()) |
| sk_image = sk_image->makeRasterImage(); |
| |
| const auto format = VideoPixelFormatFromSkColorType( |
| sk_image->colorType(), sk_image->isOpaque() || force_opaque); |
| if (VideoFrameLayout::NumPlanes(format) != 1) { |
| DLOG(ERROR) << "Invalid SkColorType for CreateFromSkImage"; |
| return nullptr; |
| } |
| |
| SkPixmap pm; |
| const bool peek_result = sk_image->peekPixels(&pm); |
| DCHECK(peek_result); |
| |
| auto coded_size = gfx::Size(sk_image->width(), sk_image->height()); |
| auto layout = VideoFrameLayout::CreateWithStrides( |
| format, coded_size, std::vector<size_t>(1, pm.rowBytes())); |
| if (!layout) |
| return nullptr; |
| |
| auto frame = VideoFrame::WrapExternalDataWithLayout( |
| *layout, visible_rect, natural_size, |
| // TODO(crbug.com/40162403): We should be able to wrap readonly memory in |
| // a VideoFrame instead of using writable_addr() here. |
| // SAFETY: We take the pointer and size from SkPixmap, we rely on Skia |
| // to give us a valid memory region. |
| UNSAFE_BUFFERS(base::span(reinterpret_cast<uint8_t*>(pm.writable_addr()), |
| pm.computeByteSize())), |
| timestamp); |
| if (!frame) |
| return nullptr; |
| |
| frame->AddDestructionObserver( |
| base::DoNothingWithBoundArgs(std::move(sk_image))); |
| return frame; |
| } |
| |
| SkYUVAInfo::PlaneConfig ToSkYUVAPlaneConfig(viz::SharedImageFormat format) { |
| using PlaneConfig = viz::SharedImageFormat::PlaneConfig; |
| switch (format.plane_config()) { |
| case PlaneConfig::kY_U_V: |
| return SkYUVAInfo::PlaneConfig::kY_U_V; |
| case PlaneConfig::kY_V_U: |
| return SkYUVAInfo::PlaneConfig::kY_V_U; |
| case PlaneConfig::kY_UV: |
| return SkYUVAInfo::PlaneConfig::kY_UV; |
| case PlaneConfig::kY_UV_A: |
| return SkYUVAInfo::PlaneConfig::kY_UV_A; |
| case PlaneConfig::kY_U_V_A: |
| return SkYUVAInfo::PlaneConfig::kY_U_V_A; |
| } |
| } |
| |
| SkYUVAInfo::Subsampling ToSkYUVASubsampling(viz::SharedImageFormat format) { |
| using Subsampling = viz::SharedImageFormat::Subsampling; |
| switch (format.subsampling()) { |
| case Subsampling::k420: |
| return SkYUVAInfo::Subsampling::k420; |
| case Subsampling::k422: |
| return SkYUVAInfo::Subsampling::k422; |
| case Subsampling::k444: |
| return SkYUVAInfo::Subsampling::k444; |
| } |
| } |
| |
| const libyuv::YuvConstants* GetYuvContantsForColorSpace( |
| SkYUVColorSpace cs, |
| bool output_argb_matrix) { |
| #define YUV_MATRIX(matrix) (output_argb_matrix ? matrix : matrix##VU) |
| switch (cs) { |
| case kJPEG_Full_SkYUVColorSpace: |
| return &YUV_MATRIX(libyuv::kYuvJPEGConstants); |
| case kRec601_Limited_SkYUVColorSpace: |
| return &YUV_MATRIX(libyuv::kYuvI601Constants); |
| case kRec709_Full_SkYUVColorSpace: |
| return &YUV_MATRIX(libyuv::kYuvF709Constants); |
| case kRec709_Limited_SkYUVColorSpace: |
| return &YUV_MATRIX(libyuv::kYuvH709Constants); |
| case kBT2020_8bit_Full_SkYUVColorSpace: |
| case kBT2020_10bit_Full_SkYUVColorSpace: |
| case kBT2020_12bit_Full_SkYUVColorSpace: |
| case kBT2020_16bit_Full_SkYUVColorSpace: |
| return &YUV_MATRIX(libyuv::kYuvV2020Constants); |
| case kBT2020_8bit_Limited_SkYUVColorSpace: |
| case kBT2020_10bit_Limited_SkYUVColorSpace: |
| case kBT2020_12bit_Limited_SkYUVColorSpace: |
| case kBT2020_16bit_Limited_SkYUVColorSpace: |
| return &YUV_MATRIX(libyuv::kYuv2020Constants); |
| case kFCC_Full_SkYUVColorSpace: |
| case kFCC_Limited_SkYUVColorSpace: |
| case kSMPTE240_Full_SkYUVColorSpace: |
| case kSMPTE240_Limited_SkYUVColorSpace: |
| case kYDZDX_Full_SkYUVColorSpace: |
| case kYDZDX_Limited_SkYUVColorSpace: |
| case kGBR_Full_SkYUVColorSpace: |
| case kGBR_Limited_SkYUVColorSpace: |
| case kYCgCo_8bit_Full_SkYUVColorSpace: |
| case kYCgCo_8bit_Limited_SkYUVColorSpace: |
| case kYCgCo_10bit_Full_SkYUVColorSpace: |
| case kYCgCo_10bit_Limited_SkYUVColorSpace: |
| case kYCgCo_12bit_Full_SkYUVColorSpace: |
| case kYCgCo_12bit_Limited_SkYUVColorSpace: |
| case kYCgCo_16bit_Full_SkYUVColorSpace: |
| case kYCgCo_16bit_Limited_SkYUVColorSpace: |
| // TODO(crbug.com/41486014): Return color space for default |
| // kRec601_SkYUVColorSpace as libyuv does not have FCC, SMPTE240M, YDZDX, |
| // GBR, YCgCo equivalent support. |
| return &YUV_MATRIX(libyuv::kYuvI601Constants); |
| case kIdentity_SkYUVColorSpace: |
| NOTREACHED(); |
| }; |
| #undef YUV_MATRIX |
| } |
| |
| } // namespace media |