| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/base/video_util.h" |
| |
| #include <cmath> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/numerics/safe_math.h" |
| #include "media/base/video_frame.h" |
| #include "third_party/libyuv/include/libyuv.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Empty method used for keeping a reference to the original media::VideoFrame. |
| void ReleaseOriginalFrame(const scoped_refptr<media::VideoFrame>& frame) {} |
| |
| // 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()) |
| 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 = data + visible_size.width(); |
| for (int i = 0; i < visible_size.height(); ++i, dst += stride) |
| std::memset(dst, *(dst - 1), pad_length); |
| } |
| |
| if (visible_size.height() < coded_size.height()) { |
| uint8_t* dst = data + visible_size.height() * stride; |
| uint8_t* src = dst - stride; |
| for (int i = visible_size.height(); i < coded_size.height(); |
| ++i, dst += stride) |
| std::memcpy(dst, src, coded_width); |
| } |
| } |
| |
| } // namespace |
| |
| gfx::Size GetNaturalSize(const gfx::Size& visible_size, |
| int aspect_ratio_numerator, |
| int aspect_ratio_denominator) { |
| if (aspect_ratio_denominator <= 0 || aspect_ratio_numerator <= 0) { |
| return gfx::Size(); |
| } |
| |
| double aspect_ratio = aspect_ratio_numerator / |
| static_cast<double>(aspect_ratio_denominator); |
| |
| // The HTML spec requires that we always grow a dimension to match aspect |
| // ratio, rather than modify just the width: |
| // github.com/whatwg/html/commit/2e94aa64fcf9adbd2f70d8c2aecd192c8678e298 |
| if (aspect_ratio_numerator > aspect_ratio_denominator) { |
| return gfx::Size(round(visible_size.width() * aspect_ratio), |
| visible_size.height()); |
| } |
| |
| return gfx::Size(visible_size.width(), |
| round(visible_size.height() / aspect_ratio)); |
| } |
| |
| void FillYUV(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v) { |
| // Fill the Y plane. |
| uint8_t* y_plane = frame->data(VideoFrame::kYPlane); |
| int y_rows = frame->rows(VideoFrame::kYPlane); |
| int y_row_bytes = frame->row_bytes(VideoFrame::kYPlane); |
| for (int i = 0; i < y_rows; ++i) { |
| memset(y_plane, y, y_row_bytes); |
| y_plane += frame->stride(VideoFrame::kYPlane); |
| } |
| |
| // Fill the U and V planes. |
| uint8_t* u_plane = frame->data(VideoFrame::kUPlane); |
| uint8_t* v_plane = frame->data(VideoFrame::kVPlane); |
| int uv_rows = frame->rows(VideoFrame::kUPlane); |
| int u_row_bytes = frame->row_bytes(VideoFrame::kUPlane); |
| int v_row_bytes = frame->row_bytes(VideoFrame::kVPlane); |
| for (int i = 0; i < uv_rows; ++i) { |
| memset(u_plane, u, u_row_bytes); |
| memset(v_plane, v, v_row_bytes); |
| u_plane += frame->stride(VideoFrame::kUPlane); |
| v_plane += frame->stride(VideoFrame::kVPlane); |
| } |
| } |
| |
| 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. |
| uint8_t* a_plane = frame->data(VideoFrame::kAPlane); |
| int a_rows = frame->rows(VideoFrame::kAPlane); |
| int a_row_bytes = frame->row_bytes(VideoFrame::kAPlane); |
| for (int i = 0; i < a_rows; ++i) { |
| memset(a_plane, a, a_row_bytes); |
| a_plane += frame->stride(VideoFrame::kAPlane); |
| } |
| } |
| |
| static void LetterboxPlane(VideoFrame* frame, |
| int plane, |
| const gfx::Rect& view_area, |
| uint8_t fill_byte) { |
| uint8_t* ptr = frame->data(plane); |
| const int rows = frame->rows(plane); |
| const int row_bytes = frame->row_bytes(plane); |
| const int stride = frame->stride(plane); |
| |
| CHECK_GE(stride, row_bytes); |
| CHECK_GE(view_area.x(), 0); |
| CHECK_GE(view_area.y(), 0); |
| CHECK_LE(view_area.right(), row_bytes); |
| CHECK_LE(view_area.bottom(), rows); |
| |
| int y = 0; |
| for (; y < view_area.y(); y++) { |
| memset(ptr, fill_byte, row_bytes); |
| ptr += stride; |
| } |
| if (view_area.width() < row_bytes) { |
| for (; y < view_area.bottom(); y++) { |
| if (view_area.x() > 0) { |
| memset(ptr, fill_byte, view_area.x()); |
| } |
| if (view_area.right() < row_bytes) { |
| memset(ptr + view_area.right(), |
| fill_byte, |
| row_bytes - view_area.right()); |
| } |
| ptr += stride; |
| } |
| } else { |
| y += view_area.height(); |
| ptr += stride * view_area.height(); |
| } |
| for (; y < rows; y++) { |
| memset(ptr, fill_byte, row_bytes); |
| ptr += stride; |
| } |
| } |
| |
| void LetterboxYUV(VideoFrame* frame, const gfx::Rect& view_area) { |
| DCHECK(!(view_area.x() & 1)); |
| DCHECK(!(view_area.y() & 1)); |
| DCHECK(!(view_area.width() & 1)); |
| DCHECK(!(view_area.height() & 1)); |
| DCHECK(frame->format() == PIXEL_FORMAT_YV12 || |
| frame->format() == PIXEL_FORMAT_I420); |
| LetterboxPlane(frame, VideoFrame::kYPlane, 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, VideoFrame::kUPlane, half_view_area, 0x80); |
| LetterboxPlane(frame, VideoFrame::kVPlane, half_view_area, 0x80); |
| } |
| |
| 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; |
| dest += height * width - 1; |
| } else { |
| dest += width - 1; |
| } |
| } else { |
| if (flip_vert) { |
| // Fast copy by rows. |
| dest += width * (height - 1); |
| for (int row = 0; row < height; ++row) { |
| memcpy(dest, src, width); |
| src += width; |
| dest -= width; |
| } |
| } else { |
| memcpy(dest, src, width * height); |
| } |
| return; |
| } |
| } else if (rotation == 90) { |
| int offset; |
| if (width > height) { |
| offset = (width - height) / 2; |
| src += offset; |
| num_rows = num_cols = height; |
| } else { |
| offset = (height - width) / 2; |
| 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) { |
| dest += (width > height ? width * (height - 1) + offset : |
| width * (height - offset - 1)); |
| } else { |
| dest += (width > height ? offset : width * offset); |
| } |
| } else { |
| if (flip_vert) { |
| dest += (width > height ? width * height - offset - 1 : |
| width * (height - offset) - 1); |
| } else { |
| 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 = *src_ptr++; |
| dest_ptr += dest_col_step; |
| } |
| src += src_stride; |
| 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::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::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())); |
| } |
| |
| void CopyRGBToVideoFrame(const uint8_t* source, |
| int stride, |
| const gfx::Rect& region_in_frame, |
| VideoFrame* frame) { |
| const int kY = VideoFrame::kYPlane; |
| const int kU = VideoFrame::kUPlane; |
| const int kV = VideoFrame::kVPlane; |
| CHECK_EQ(frame->stride(kU), frame->stride(kV)); |
| const int uv_stride = frame->stride(kU); |
| |
| if (region_in_frame != gfx::Rect(frame->coded_size())) { |
| LetterboxYUV(frame, region_in_frame); |
| } |
| |
| const int y_offset = |
| region_in_frame.x() + (region_in_frame.y() * frame->stride(kY)); |
| const int uv_offset = |
| region_in_frame.x() / 2 + (region_in_frame.y() / 2 * uv_stride); |
| |
| libyuv::ARGBToI420(source, stride, frame->data(kY) + y_offset, |
| frame->stride(kY), frame->data(kU) + uv_offset, uv_stride, |
| frame->data(kV) + uv_offset, uv_stride, |
| region_in_frame.width(), region_in_frame.height()); |
| } |
| |
| scoped_refptr<VideoFrame> WrapAsI420VideoFrame( |
| const scoped_refptr<VideoFrame>& frame) { |
| DCHECK_EQ(VideoFrame::STORAGE_OWNED_MEMORY, frame->storage_type()); |
| DCHECK_EQ(PIXEL_FORMAT_I420A, frame->format()); |
| |
| scoped_refptr<media::VideoFrame> wrapped_frame = |
| media::VideoFrame::WrapVideoFrame(frame, PIXEL_FORMAT_I420, |
| frame->visible_rect(), |
| frame->natural_size()); |
| if (!wrapped_frame) |
| return nullptr; |
| wrapped_frame->AddDestructionObserver( |
| base::Bind(&ReleaseOriginalFrame, frame)); |
| 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::kYPlane), |
| src_frame.stride(VideoFrame::kYPlane), |
| src_frame.visible_data(VideoFrame::kUPlane), |
| src_frame.stride(VideoFrame::kUPlane), |
| src_frame.visible_data(VideoFrame::kVPlane), |
| src_frame.stride(VideoFrame::kVPlane), |
| dst_frame->data(VideoFrame::kYPlane), |
| dst_frame->stride(VideoFrame::kYPlane), |
| dst_frame->data(VideoFrame::kUPlane), |
| dst_frame->stride(VideoFrame::kUPlane), |
| dst_frame->data(VideoFrame::kVPlane), |
| dst_frame->stride(VideoFrame::kVPlane), |
| 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->data(VideoFrame::kYPlane), |
| dst_frame->stride(VideoFrame::kYPlane), |
| dst_frame->coded_size(), |
| src_frame.visible_rect().size()); |
| FillRegionOutsideVisibleRect( |
| dst_frame->data(VideoFrame::kUPlane), |
| dst_frame->stride(VideoFrame::kUPlane), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::kUPlane, |
| dst_frame->coded_size()), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::kUPlane, |
| src_frame.visible_rect().size())); |
| FillRegionOutsideVisibleRect( |
| dst_frame->data(VideoFrame::kVPlane), |
| dst_frame->stride(VideoFrame::kVPlane), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::kVPlane, |
| dst_frame->coded_size()), |
| VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::kVPlane, |
| src_frame.visible_rect().size())); |
| |
| return true; |
| } |
| |
| } // namespace media |