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