| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/filters/skcanvas_video_renderer.h" | 5 #include "media/filters/skcanvas_video_renderer.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "media/base/video_frame.h" | 8 #include "media/base/video_frame.h" |
| 9 #include "media/base/yuv_convert.h" | 9 #include "media/base/yuv_convert.h" |
| 10 #include "third_party/libyuv/include/libyuv.h" | 10 #include "third_party/libyuv/include/libyuv.h" |
| 11 #include "third_party/skia/include/core/SkCanvas.h" | 11 #include "third_party/skia/include/core/SkCanvas.h" |
| 12 #include "third_party/skia/include/core/SkDevice.h" | |
| 13 | 12 |
| 14 // Skia internal format depends on a platform. On Android it is ABGR, on others | 13 // Skia internal format depends on a platform. On Android it is ABGR, on others |
| 15 // it is ARGB. | 14 // it is ARGB. |
| 16 #if SK_B32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_R32_SHIFT == 16 && \ | 15 #if SK_B32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_R32_SHIFT == 16 && \ |
| 17 SK_A32_SHIFT == 24 | 16 SK_A32_SHIFT == 24 |
| 18 #define LIBYUV_I420_TO_ARGB libyuv::I420ToARGB | 17 #define LIBYUV_I420_TO_ARGB libyuv::I420ToARGB |
| 19 #define LIBYUV_I422_TO_ARGB libyuv::I422ToARGB | 18 #define LIBYUV_I422_TO_ARGB libyuv::I422ToARGB |
| 20 #elif SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \ | 19 #elif SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \ |
| 21 SK_A32_SHIFT == 24 | 20 SK_A32_SHIFT == 24 |
| 22 #define LIBYUV_I420_TO_ARGB libyuv::I420ToABGR | 21 #define LIBYUV_I420_TO_ARGB libyuv::I420ToABGR |
| 23 #define LIBYUV_I422_TO_ARGB libyuv::I422ToABGR | 22 #define LIBYUV_I422_TO_ARGB libyuv::I422ToABGR |
| 24 #else | 23 #else |
| 25 #error Unexpected Skia ARGB_8888 layout! | 24 #error Unexpected Skia ARGB_8888 layout! |
| 26 #endif | 25 #endif |
| 27 | 26 |
| 28 namespace media { | 27 namespace media { |
| 29 | 28 |
| 30 static bool IsYUV(media::VideoFrame::Format format) { | 29 static bool IsYUV(media::VideoFrame::Format format) { |
| 31 return format == media::VideoFrame::YV12 || | 30 return format == media::VideoFrame::YV12 || |
| 32 format == media::VideoFrame::YV16 || | 31 format == media::VideoFrame::YV16 || |
| 33 format == media::VideoFrame::I420 || | 32 format == media::VideoFrame::I420 || |
| 34 format == media::VideoFrame::YV12A || | 33 format == media::VideoFrame::YV12A || |
| 35 format == media::VideoFrame::YV12J || | 34 format == media::VideoFrame::YV12J || |
| 36 format == media::VideoFrame::YV24; | 35 format == media::VideoFrame::YV24; |
| 37 } | 36 } |
| 38 | 37 |
| 39 static bool IsFastPaintYUV(media::VideoFrame::Format format) { | |
| 40 return format == media::VideoFrame::YV12 || | |
| 41 format == media::VideoFrame::YV16 || | |
| 42 format == media::VideoFrame::I420 || | |
| 43 format == media::VideoFrame::YV12J; | |
| 44 } | |
| 45 | |
| 46 static bool IsYUVOrNative(media::VideoFrame::Format format) { | 38 static bool IsYUVOrNative(media::VideoFrame::Format format) { |
| 47 return IsYUV(format) || format == media::VideoFrame::NATIVE_TEXTURE; | 39 return IsYUV(format) || format == media::VideoFrame::NATIVE_TEXTURE; |
| 48 } | 40 } |
| 49 | 41 |
| 50 // CanFastPaint is a helper method to determine the conditions for fast | |
| 51 // painting. The conditions are: | |
| 52 // 1. No skew in canvas matrix. | |
| 53 // 2. No flipping nor mirroring. | |
| 54 // 3. Canvas has pixel format ARGB8888. | |
| 55 // 4. Canvas is opaque. | |
| 56 // 5. Frame format is YV12, I420 or YV16. | |
| 57 // | |
| 58 // TODO(hclam): The fast paint method should support flipping and mirroring. | |
| 59 // Disable the flipping and mirroring checks once we have it. | |
| 60 static bool CanFastPaint(SkCanvas* canvas, uint8 alpha, | |
| 61 media::VideoFrame::Format format) { | |
| 62 if (alpha != 0xFF || !IsFastPaintYUV(format)) | |
| 63 return false; | |
| 64 | |
| 65 const SkMatrix& total_matrix = canvas->getTotalMatrix(); | |
| 66 // Perform the following checks here: | |
| 67 // 1. Check for skewing factors of the transformation matrix. They should be | |
| 68 // zero. | |
| 69 // 2. Check for mirroring and flipping. Make sure they are greater than zero. | |
| 70 if (SkScalarNearlyZero(total_matrix.getSkewX()) && | |
| 71 SkScalarNearlyZero(total_matrix.getSkewY()) && | |
| 72 total_matrix.getScaleX() > 0 && | |
| 73 total_matrix.getScaleY() > 0) { | |
| 74 const SkImageInfo info = canvas->imageInfo(); | |
| 75 | |
| 76 if (info.colorType() == kN32_SkColorType && info.isOpaque()) { | |
| 77 return true; | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 return false; | |
| 82 } | |
| 83 | |
| 84 // Fast paint does YUV => RGB, scaling, blitting all in one step into the | |
| 85 // canvas. It's not always safe and appropriate to perform fast paint. | |
| 86 // CanFastPaint() is used to determine the conditions. | |
| 87 static void FastPaint( | |
| 88 const scoped_refptr<media::VideoFrame>& video_frame, | |
| 89 SkCanvas* canvas, | |
| 90 const SkRect& dest_rect) { | |
| 91 DCHECK(IsFastPaintYUV(video_frame->format())) << video_frame->format(); | |
| 92 DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane), | |
| 93 video_frame->stride(media::VideoFrame::kVPlane)); | |
| 94 | |
| 95 const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true); | |
| 96 media::YUVType yuv_type = media::YV16; | |
| 97 int y_shift = 0; | |
| 98 if (video_frame->format() == media::VideoFrame::YV12 || | |
| 99 video_frame->format() == media::VideoFrame::I420) { | |
| 100 yuv_type = media::YV12; | |
| 101 y_shift = 1; | |
| 102 } | |
| 103 | |
| 104 if (video_frame->format() == media::VideoFrame::YV12J) { | |
| 105 yuv_type = media::YV12J; | |
| 106 y_shift = 1; | |
| 107 } | |
| 108 | |
| 109 // Transform the destination rectangle to local coordinates. | |
| 110 const SkMatrix& local_matrix = canvas->getTotalMatrix(); | |
| 111 SkRect local_dest_rect; | |
| 112 local_matrix.mapRect(&local_dest_rect, dest_rect); | |
| 113 | |
| 114 // After projecting the destination rectangle to local coordinates, round | |
| 115 // the projected rectangle to integer values, this will give us pixel values | |
| 116 // of the rectangle. | |
| 117 SkIRect local_dest_irect, local_dest_irect_saved; | |
| 118 local_dest_rect.round(&local_dest_irect); | |
| 119 local_dest_rect.round(&local_dest_irect_saved); | |
| 120 | |
| 121 // No point painting if the destination rect doesn't intersect with the | |
| 122 // clip rect. | |
| 123 SkIRect device_bounds; | |
| 124 if (!canvas->getClipDeviceBounds(&device_bounds)) | |
| 125 return; | |
| 126 if (!local_dest_irect.intersect(device_bounds)) | |
| 127 return; | |
| 128 | |
| 129 // At this point |local_dest_irect| contains the rect that we should draw | |
| 130 // to within the clipping rect. | |
| 131 | |
| 132 // Project the clip rect to the original video frame, obtains the | |
| 133 // dimensions of the projected clip rect, "left" and "top" of the rect. | |
| 134 // The math here are all integer math so we won't have rounding error and | |
| 135 // write outside of the canvas. | |
| 136 // We have the assumptions of dest_rect.width() and dest_rect.height() | |
| 137 // being non-zero, these are valid assumptions since finding intersection | |
| 138 // above rejects empty rectangle so we just do a DCHECK here. | |
| 139 DCHECK_NE(0, dest_rect.width()); | |
| 140 DCHECK_NE(0, dest_rect.height()); | |
| 141 size_t frame_clip_width = local_dest_irect.width() * | |
| 142 video_frame->visible_rect().width() / | |
| 143 local_dest_irect_saved.width(); | |
| 144 size_t frame_clip_height = local_dest_irect.height() * | |
| 145 video_frame->visible_rect().height() / | |
| 146 local_dest_irect_saved.height(); | |
| 147 | |
| 148 // Project the "left" and "top" of the final destination rect to local | |
| 149 // coordinates of the video frame, use these values to find the offsets | |
| 150 // in the video frame to start reading. | |
| 151 size_t frame_clip_left = | |
| 152 video_frame->visible_rect().x() + | |
| 153 (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) * | |
| 154 video_frame->visible_rect().width() / local_dest_irect_saved.width(); | |
| 155 size_t frame_clip_top = | |
| 156 video_frame->visible_rect().y() + | |
| 157 (local_dest_irect.fTop - local_dest_irect_saved.fTop) * | |
| 158 video_frame->visible_rect().height() / | |
| 159 local_dest_irect_saved.height(); | |
| 160 | |
| 161 // Use the "left" and "top" of the destination rect to locate the offset | |
| 162 // in Y, U and V planes. | |
| 163 size_t y_offset = | |
| 164 (video_frame->stride(media::VideoFrame::kYPlane) * frame_clip_top) + | |
| 165 frame_clip_left; | |
| 166 | |
| 167 // For format YV12, there is one U, V value per 2x2 block. | |
| 168 // For format YV16, there is one U, V value per 2x1 block. | |
| 169 size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) * | |
| 170 (frame_clip_top >> y_shift)) + | |
| 171 (frame_clip_left >> 1); | |
| 172 | |
| 173 // Calculate the address for the top left corner of destination rect in | |
| 174 // the canvas that we will draw to. The address is obtained by the base | |
| 175 // address of the canvas shifted by "left" and "top" of the rect. | |
| 176 uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) + | |
| 177 local_dest_irect.fTop * bitmap.rowBytes() + | |
| 178 local_dest_irect.fLeft * 4; | |
| 179 | |
| 180 uint8* frame_clip_y = | |
| 181 video_frame->data(media::VideoFrame::kYPlane) + y_offset; | |
| 182 uint8* frame_clip_u = | |
| 183 video_frame->data(media::VideoFrame::kUPlane) + uv_offset; | |
| 184 uint8* frame_clip_v = | |
| 185 video_frame->data(media::VideoFrame::kVPlane) + uv_offset; | |
| 186 | |
| 187 // TODO(hclam): do rotation and mirroring here. | |
| 188 // TODO(fbarchard): switch filtering based on performance. | |
| 189 bitmap.lockPixels(); | |
| 190 | |
| 191 // If there is no scaling and the frame format is supported, then use faster | |
| 192 // libyuv. | |
| 193 if (frame_clip_width == static_cast<size_t>(local_dest_irect.width()) && | |
| 194 frame_clip_height == static_cast<size_t>(local_dest_irect.height())) { | |
| 195 switch (yuv_type) { | |
| 196 case media::YV12: | |
| 197 LIBYUV_I420_TO_ARGB(frame_clip_y, | |
| 198 video_frame->stride(media::VideoFrame::kYPlane), | |
| 199 frame_clip_u, | |
| 200 video_frame->stride(media::VideoFrame::kUPlane), | |
| 201 frame_clip_v, | |
| 202 video_frame->stride(media::VideoFrame::kVPlane), | |
| 203 dest_rect_pointer, | |
| 204 bitmap.rowBytes(), | |
| 205 local_dest_irect.width(), | |
| 206 local_dest_irect.height()); | |
| 207 bitmap.unlockPixels(); | |
| 208 return; | |
| 209 | |
| 210 case media::YV16: | |
| 211 LIBYUV_I422_TO_ARGB(frame_clip_y, | |
| 212 video_frame->stride(media::VideoFrame::kYPlane), | |
| 213 frame_clip_u, | |
| 214 video_frame->stride(media::VideoFrame::kUPlane), | |
| 215 frame_clip_v, | |
| 216 video_frame->stride(media::VideoFrame::kVPlane), | |
| 217 dest_rect_pointer, | |
| 218 bitmap.rowBytes(), | |
| 219 local_dest_irect.width(), | |
| 220 local_dest_irect.height()); | |
| 221 bitmap.unlockPixels(); | |
| 222 return; | |
| 223 | |
| 224 default: | |
| 225 break; | |
| 226 } | |
| 227 } | |
| 228 | |
| 229 // Fallback to media, since the operation is not supported by libyuv. | |
| 230 media::ScaleYUVToRGB32(frame_clip_y, | |
| 231 frame_clip_u, | |
| 232 frame_clip_v, | |
| 233 dest_rect_pointer, | |
| 234 frame_clip_width, | |
| 235 frame_clip_height, | |
| 236 local_dest_irect.width(), | |
| 237 local_dest_irect.height(), | |
| 238 video_frame->stride(media::VideoFrame::kYPlane), | |
| 239 video_frame->stride(media::VideoFrame::kUPlane), | |
| 240 bitmap.rowBytes(), | |
| 241 yuv_type, | |
| 242 media::ROTATE_0, | |
| 243 media::FILTER_BILINEAR); | |
| 244 bitmap.unlockPixels(); | |
| 245 } | |
| 246 | |
| 247 // Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data. | 42 // Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data. |
| 248 // | 43 // |
| 249 // |bitmap| will be (re)allocated to match the dimensions of |video_frame|. | 44 // |bitmap| will be (re)allocated to match the dimensions of |video_frame|. |
| 250 static void ConvertVideoFrameToBitmap( | 45 static void ConvertVideoFrameToBitmap( |
| 251 const scoped_refptr<media::VideoFrame>& video_frame, | 46 const scoped_refptr<media::VideoFrame>& video_frame, |
| 252 SkBitmap* bitmap) { | 47 SkBitmap* bitmap) { |
| 253 DCHECK(IsYUVOrNative(video_frame->format())) | 48 DCHECK(IsYUVOrNative(video_frame->format())) |
| 254 << video_frame->format(); | 49 << video_frame->format(); |
| 255 if (IsYUV(video_frame->format())) { | 50 if (IsYUV(video_frame->format())) { |
| 256 DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane), | 51 DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane), |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 406 SkPaint paint; | 201 SkPaint paint; |
| 407 paint.setAlpha(alpha); | 202 paint.setAlpha(alpha); |
| 408 | 203 |
| 409 // Paint black rectangle if there isn't a frame available or the | 204 // Paint black rectangle if there isn't a frame available or the |
| 410 // frame has an unexpected format. | 205 // frame has an unexpected format. |
| 411 if (!video_frame || !IsYUVOrNative(video_frame->format())) { | 206 if (!video_frame || !IsYUVOrNative(video_frame->format())) { |
| 412 canvas->drawRect(dest, paint); | 207 canvas->drawRect(dest, paint); |
| 413 return; | 208 return; |
| 414 } | 209 } |
| 415 | 210 |
| 416 // Scale and convert to RGB in one step if we can. | |
| 417 if (CanFastPaint(canvas, alpha, video_frame->format())) { | |
| 418 FastPaint(video_frame, canvas, dest); | |
| 419 return; | |
| 420 } | |
| 421 | |
| 422 // Check if we should convert and update |last_frame_|. | 211 // Check if we should convert and update |last_frame_|. |
| 423 if (last_frame_.isNull() || | 212 if (last_frame_.isNull() || |
| 424 video_frame->timestamp() != last_frame_timestamp_) { | 213 video_frame->timestamp() != last_frame_timestamp_) { |
| 425 ConvertVideoFrameToBitmap(video_frame, &last_frame_); | 214 ConvertVideoFrameToBitmap(video_frame, &last_frame_); |
| 426 last_frame_timestamp_ = video_frame->timestamp(); | 215 last_frame_timestamp_ = video_frame->timestamp(); |
| 427 } | 216 } |
| 428 | 217 |
| 429 // Paint using |last_frame_|. | 218 // Paint using |last_frame_|. |
| 430 paint.setFilterLevel(SkPaint::kLow_FilterLevel); | 219 paint.setFilterLevel(SkPaint::kLow_FilterLevel); |
| 431 canvas->drawBitmapRect(last_frame_, NULL, dest, &paint); | 220 canvas->drawBitmapRect(last_frame_, NULL, dest, &paint); |
| 432 } | 221 } |
| 433 | 222 |
| 434 } // namespace media | 223 } // namespace media |
| OLD | NEW |