| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "webkit/media/skcanvas_video_renderer.h" | |
| 6 | |
| 7 #include "base/logging.h" | |
| 8 #include "media/base/video_frame.h" | |
| 9 #include "media/base/yuv_convert.h" | |
| 10 #include "third_party/skia/include/core/SkCanvas.h" | |
| 11 #include "third_party/skia/include/core/SkDevice.h" | |
| 12 | |
| 13 namespace webkit_media { | |
| 14 | |
| 15 static bool IsEitherYV12OrYV16(media::VideoFrame::Format format) { | |
| 16 return format == media::VideoFrame::YV12 || format == media::VideoFrame::YV16; | |
| 17 } | |
| 18 | |
| 19 static bool IsEitherYV12OrYV16OrNative(media::VideoFrame::Format format) { | |
| 20 return IsEitherYV12OrYV16(format) || | |
| 21 format == media::VideoFrame::NATIVE_TEXTURE; | |
| 22 } | |
| 23 | |
| 24 // CanFastPaint is a helper method to determine the conditions for fast | |
| 25 // painting. The conditions are: | |
| 26 // 1. No skew in canvas matrix. | |
| 27 // 2. No flipping nor mirroring. | |
| 28 // 3. Canvas has pixel format ARGB8888. | |
| 29 // 4. Canvas is opaque. | |
| 30 // 5. Frame format is YV12 or YV16. | |
| 31 // | |
| 32 // TODO(hclam): The fast paint method should support flipping and mirroring. | |
| 33 // Disable the flipping and mirroring checks once we have it. | |
| 34 static bool CanFastPaint(SkCanvas* canvas, const gfx::Rect& dest_rect, | |
| 35 uint8_t alpha, media::VideoFrame::Format format) { | |
| 36 if (alpha != 0xFF || !IsEitherYV12OrYV16(format)) | |
| 37 return false; | |
| 38 | |
| 39 const SkMatrix& total_matrix = canvas->getTotalMatrix(); | |
| 40 // Perform the following checks here: | |
| 41 // 1. Check for skewing factors of the transformation matrix. They should be | |
| 42 // zero. | |
| 43 // 2. Check for mirroring and flipping. Make sure they are greater than zero. | |
| 44 if (SkScalarNearlyZero(total_matrix.getSkewX()) && | |
| 45 SkScalarNearlyZero(total_matrix.getSkewY()) && | |
| 46 total_matrix.getScaleX() > 0 && | |
| 47 total_matrix.getScaleY() > 0) { | |
| 48 SkDevice* device = canvas->getDevice(); | |
| 49 const SkBitmap::Config config = device->config(); | |
| 50 | |
| 51 if (config == SkBitmap::kARGB_8888_Config && device->isOpaque()) { | |
| 52 return true; | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 return false; | |
| 57 } | |
| 58 | |
| 59 // Fast paint does YUV => RGB, scaling, blitting all in one step into the | |
| 60 // canvas. It's not always safe and appropriate to perform fast paint. | |
| 61 // CanFastPaint() is used to determine the conditions. | |
| 62 static void FastPaint( | |
| 63 const scoped_refptr<media::VideoFrame>& video_frame, | |
| 64 SkCanvas* canvas, | |
| 65 const gfx::Rect& dest_rect) { | |
| 66 DCHECK(IsEitherYV12OrYV16(video_frame->format())) << video_frame->format(); | |
| 67 DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane), | |
| 68 video_frame->stride(media::VideoFrame::kVPlane)); | |
| 69 | |
| 70 const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true); | |
| 71 media::YUVType yuv_type = (video_frame->format() == media::VideoFrame::YV12) ? | |
| 72 media::YV12 : media::YV16; | |
| 73 int y_shift = yuv_type; // 1 for YV12, 0 for YV16. | |
| 74 | |
| 75 // Create a rectangle backed by SkScalar. | |
| 76 SkRect scalar_dest_rect; | |
| 77 scalar_dest_rect.iset(dest_rect.x(), dest_rect.y(), | |
| 78 dest_rect.right(), dest_rect.bottom()); | |
| 79 | |
| 80 // Transform the destination rectangle to local coordinates. | |
| 81 const SkMatrix& local_matrix = canvas->getTotalMatrix(); | |
| 82 SkRect local_dest_rect; | |
| 83 local_matrix.mapRect(&local_dest_rect, scalar_dest_rect); | |
| 84 | |
| 85 // After projecting the destination rectangle to local coordinates, round | |
| 86 // the projected rectangle to integer values, this will give us pixel values | |
| 87 // of the rectangle. | |
| 88 SkIRect local_dest_irect, local_dest_irect_saved; | |
| 89 local_dest_rect.round(&local_dest_irect); | |
| 90 local_dest_rect.round(&local_dest_irect_saved); | |
| 91 | |
| 92 // No point painting if the destination rect doesn't intersect with the | |
| 93 // clip rect. | |
| 94 if (!local_dest_irect.intersect(canvas->getTotalClip().getBounds())) | |
| 95 return; | |
| 96 | |
| 97 // At this point |local_dest_irect| contains the rect that we should draw | |
| 98 // to within the clipping rect. | |
| 99 | |
| 100 // Calculate the address for the top left corner of destination rect in | |
| 101 // the canvas that we will draw to. The address is obtained by the base | |
| 102 // address of the canvas shifted by "left" and "top" of the rect. | |
| 103 uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) + | |
| 104 local_dest_irect.fTop * bitmap.rowBytes() + | |
| 105 local_dest_irect.fLeft * 4; | |
| 106 | |
| 107 // Project the clip rect to the original video frame, obtains the | |
| 108 // dimensions of the projected clip rect, "left" and "top" of the rect. | |
| 109 // The math here are all integer math so we won't have rounding error and | |
| 110 // write outside of the canvas. | |
| 111 // We have the assumptions of dest_rect.width() and dest_rect.height() | |
| 112 // being non-zero, these are valid assumptions since finding intersection | |
| 113 // above rejects empty rectangle so we just do a DCHECK here. | |
| 114 DCHECK_NE(0, dest_rect.width()); | |
| 115 DCHECK_NE(0, dest_rect.height()); | |
| 116 size_t frame_clip_width = local_dest_irect.width() * | |
| 117 video_frame->data_size().width() / local_dest_irect_saved.width(); | |
| 118 size_t frame_clip_height = local_dest_irect.height() * | |
| 119 video_frame->data_size().height() / local_dest_irect_saved.height(); | |
| 120 | |
| 121 // Project the "left" and "top" of the final destination rect to local | |
| 122 // coordinates of the video frame, use these values to find the offsets | |
| 123 // in the video frame to start reading. | |
| 124 size_t frame_clip_left = | |
| 125 (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) * | |
| 126 video_frame->data_size().width() / local_dest_irect_saved.width(); | |
| 127 size_t frame_clip_top = | |
| 128 (local_dest_irect.fTop - local_dest_irect_saved.fTop) * | |
| 129 video_frame->data_size().height() / local_dest_irect_saved.height(); | |
| 130 | |
| 131 // Use the "left" and "top" of the destination rect to locate the offset | |
| 132 // in Y, U and V planes. | |
| 133 size_t y_offset = video_frame->stride(media::VideoFrame::kYPlane) * | |
| 134 frame_clip_top + frame_clip_left; | |
| 135 | |
| 136 // For format YV12, there is one U, V value per 2x2 block. | |
| 137 // For format YV16, there is one u, V value per 2x1 block. | |
| 138 size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) * | |
| 139 (frame_clip_top >> y_shift)) + (frame_clip_left >> 1); | |
| 140 uint8* frame_clip_y = | |
| 141 video_frame->data(media::VideoFrame::kYPlane) + y_offset; | |
| 142 uint8* frame_clip_u = | |
| 143 video_frame->data(media::VideoFrame::kUPlane) + uv_offset; | |
| 144 uint8* frame_clip_v = | |
| 145 video_frame->data(media::VideoFrame::kVPlane) + uv_offset; | |
| 146 | |
| 147 // TODO(hclam): do rotation and mirroring here. | |
| 148 // TODO(fbarchard): switch filtering based on performance. | |
| 149 bitmap.lockPixels(); | |
| 150 media::ScaleYUVToRGB32(frame_clip_y, | |
| 151 frame_clip_u, | |
| 152 frame_clip_v, | |
| 153 dest_rect_pointer, | |
| 154 frame_clip_width, | |
| 155 frame_clip_height, | |
| 156 local_dest_irect.width(), | |
| 157 local_dest_irect.height(), | |
| 158 video_frame->stride(media::VideoFrame::kYPlane), | |
| 159 video_frame->stride(media::VideoFrame::kUPlane), | |
| 160 bitmap.rowBytes(), | |
| 161 yuv_type, | |
| 162 media::ROTATE_0, | |
| 163 media::FILTER_BILINEAR); | |
| 164 bitmap.unlockPixels(); | |
| 165 } | |
| 166 | |
| 167 // Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data. | |
| 168 // | |
| 169 // |bitmap| will be (re)allocated to match the dimensions of |video_frame|. | |
| 170 static void ConvertVideoFrameToBitmap( | |
| 171 const scoped_refptr<media::VideoFrame>& video_frame, | |
| 172 SkBitmap* bitmap) { | |
| 173 DCHECK(IsEitherYV12OrYV16OrNative(video_frame->format())) | |
| 174 << video_frame->format(); | |
| 175 if (IsEitherYV12OrYV16(video_frame->format())) { | |
| 176 DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane), | |
| 177 video_frame->stride(media::VideoFrame::kVPlane)); | |
| 178 } | |
| 179 | |
| 180 // Check if |bitmap| needs to be (re)allocated. | |
| 181 if (bitmap->isNull() || | |
| 182 bitmap->width() != video_frame->data_size().width() || | |
| 183 bitmap->height() != video_frame->data_size().height()) { | |
| 184 bitmap->setConfig(SkBitmap::kARGB_8888_Config, | |
| 185 video_frame->data_size().width(), | |
| 186 video_frame->data_size().height()); | |
| 187 bitmap->allocPixels(); | |
| 188 bitmap->setIsVolatile(true); | |
| 189 } | |
| 190 | |
| 191 bitmap->lockPixels(); | |
| 192 if (IsEitherYV12OrYV16(video_frame->format())) { | |
| 193 media::YUVType yuv_type = | |
| 194 (video_frame->format() == media::VideoFrame::YV12) ? | |
| 195 media::YV12 : media::YV16; | |
| 196 media::ConvertYUVToRGB32(video_frame->data(media::VideoFrame::kYPlane), | |
| 197 video_frame->data(media::VideoFrame::kUPlane), | |
| 198 video_frame->data(media::VideoFrame::kVPlane), | |
| 199 static_cast<uint8*>(bitmap->getPixels()), | |
| 200 video_frame->data_size().width(), | |
| 201 video_frame->data_size().height(), | |
| 202 video_frame->stride(media::VideoFrame::kYPlane), | |
| 203 video_frame->stride(media::VideoFrame::kUPlane), | |
| 204 bitmap->rowBytes(), | |
| 205 yuv_type); | |
| 206 } else { | |
| 207 DCHECK_EQ(video_frame->format(), media::VideoFrame::NATIVE_TEXTURE); | |
| 208 video_frame->ReadPixelsFromNativeTexture(bitmap->getPixels()); | |
| 209 } | |
| 210 bitmap->notifyPixelsChanged(); | |
| 211 bitmap->unlockPixels(); | |
| 212 } | |
| 213 | |
| 214 SkCanvasVideoRenderer::SkCanvasVideoRenderer() | |
| 215 : last_frame_timestamp_(media::kNoTimestamp()) { | |
| 216 } | |
| 217 | |
| 218 SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {} | |
| 219 | |
| 220 void SkCanvasVideoRenderer::Paint(media::VideoFrame* video_frame, | |
| 221 SkCanvas* canvas, | |
| 222 const gfx::Rect& dest_rect, | |
| 223 uint8_t alpha) { | |
| 224 if (alpha == 0) { | |
| 225 return; | |
| 226 } | |
| 227 | |
| 228 SkRect dest; | |
| 229 dest.set(SkIntToScalar(dest_rect.x()), SkIntToScalar(dest_rect.y()), | |
| 230 SkIntToScalar(dest_rect.right()), SkIntToScalar(dest_rect.bottom())); | |
| 231 | |
| 232 SkPaint paint; | |
| 233 paint.setAlpha(alpha); | |
| 234 | |
| 235 // Paint black rectangle if there isn't a frame available or the | |
| 236 // frame has an unexpected format. | |
| 237 if (!video_frame || !IsEitherYV12OrYV16OrNative(video_frame->format())) { | |
| 238 canvas->drawRect(dest, paint); | |
| 239 return; | |
| 240 } | |
| 241 | |
| 242 // Scale and convert to RGB in one step if we can. | |
| 243 if (CanFastPaint(canvas, dest_rect, alpha, video_frame->format())) { | |
| 244 FastPaint(video_frame, canvas, dest_rect); | |
| 245 return; | |
| 246 } | |
| 247 | |
| 248 // Check if we should convert and update |last_frame_|. | |
| 249 if (last_frame_.isNull() || | |
| 250 video_frame->GetTimestamp() != last_frame_timestamp_) { | |
| 251 ConvertVideoFrameToBitmap(video_frame, &last_frame_); | |
| 252 last_frame_timestamp_ = video_frame->GetTimestamp(); | |
| 253 } | |
| 254 | |
| 255 // Do a slower paint using |last_frame_|. | |
| 256 paint.setFilterBitmap(true); | |
| 257 canvas->drawBitmapRect(last_frame_, NULL, dest, &paint); | |
| 258 } | |
| 259 | |
| 260 } // namespace webkit_media | |
| OLD | NEW |