blob: 003c1951a9b4010e93e612a347d645b22aa3f66a [file] [log] [blame]
// 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/filters/skcanvas_video_renderer.h"
#include "base/logging.h"
#include "media/base/video_frame.h"
#include "media/base/yuv_convert.h"
#include "third_party/libyuv/include/libyuv.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/gfx/skbitmap_operations.h"
// Skia internal format depends on a platform. On Android it is ABGR, on others
// it is ARGB.
#if SK_B32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_R32_SHIFT == 16 && \
SK_A32_SHIFT == 24
#define LIBYUV_I420_TO_ARGB libyuv::I420ToARGB
#define LIBYUV_I422_TO_ARGB libyuv::I422ToARGB
#elif SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \
SK_A32_SHIFT == 24
#define LIBYUV_I420_TO_ARGB libyuv::I420ToABGR
#define LIBYUV_I422_TO_ARGB libyuv::I422ToABGR
#else
#error Unexpected Skia ARGB_8888 layout!
#endif
namespace media {
static bool IsYUV(media::VideoFrame::Format format) {
return format == media::VideoFrame::YV12 ||
format == media::VideoFrame::YV16 ||
format == media::VideoFrame::I420 ||
format == media::VideoFrame::YV12A ||
format == media::VideoFrame::YV12J ||
format == media::VideoFrame::YV24;
}
static bool IsYUVOrNative(media::VideoFrame::Format format) {
return IsYUV(format) || format == media::VideoFrame::NATIVE_TEXTURE;
}
// Converts a VideoFrame containing YUV data to a SkBitmap containing RGB data.
//
// |bitmap| will be (re)allocated to match the dimensions of |video_frame|.
static void ConvertVideoFrameToBitmap(
const scoped_refptr<media::VideoFrame>& video_frame,
SkBitmap* bitmap) {
DCHECK(IsYUVOrNative(video_frame->format()))
<< video_frame->format();
if (IsYUV(video_frame->format())) {
DCHECK_EQ(video_frame->stride(media::VideoFrame::kUPlane),
video_frame->stride(media::VideoFrame::kVPlane));
}
// Check if |bitmap| needs to be (re)allocated.
if (bitmap->isNull() ||
bitmap->width() != video_frame->visible_rect().width() ||
bitmap->height() != video_frame->visible_rect().height()) {
bitmap->allocN32Pixels(video_frame->visible_rect().width(),
video_frame->visible_rect().height());
bitmap->setIsVolatile(true);
}
bitmap->lockPixels();
size_t y_offset = 0;
size_t uv_offset = 0;
if (IsYUV(video_frame->format())) {
int y_shift = (video_frame->format() == media::VideoFrame::YV16) ? 0 : 1;
// Use the "left" and "top" of the destination rect to locate the offset
// in Y, U and V planes.
y_offset = (video_frame->stride(media::VideoFrame::kYPlane) *
video_frame->visible_rect().y()) +
video_frame->visible_rect().x();
// For format YV12, there is one U, V value per 2x2 block.
// For format YV16, there is one U, V value per 2x1 block.
uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) *
(video_frame->visible_rect().y() >> y_shift)) +
(video_frame->visible_rect().x() >> 1);
}
switch (video_frame->format()) {
case media::VideoFrame::YV12:
case media::VideoFrame::I420:
LIBYUV_I420_TO_ARGB(
video_frame->data(media::VideoFrame::kYPlane) + y_offset,
video_frame->stride(media::VideoFrame::kYPlane),
video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
video_frame->stride(media::VideoFrame::kUPlane),
video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
video_frame->stride(media::VideoFrame::kVPlane),
static_cast<uint8*>(bitmap->getPixels()),
bitmap->rowBytes(),
video_frame->visible_rect().width(),
video_frame->visible_rect().height());
break;
case media::VideoFrame::YV12J:
media::ConvertYUVToRGB32(
video_frame->data(media::VideoFrame::kYPlane) + y_offset,
video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
static_cast<uint8*>(bitmap->getPixels()),
video_frame->visible_rect().width(),
video_frame->visible_rect().height(),
video_frame->stride(media::VideoFrame::kYPlane),
video_frame->stride(media::VideoFrame::kUPlane),
bitmap->rowBytes(),
media::YV12J);
break;
case media::VideoFrame::YV16:
LIBYUV_I422_TO_ARGB(
video_frame->data(media::VideoFrame::kYPlane) + y_offset,
video_frame->stride(media::VideoFrame::kYPlane),
video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
video_frame->stride(media::VideoFrame::kUPlane),
video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
video_frame->stride(media::VideoFrame::kVPlane),
static_cast<uint8*>(bitmap->getPixels()),
bitmap->rowBytes(),
video_frame->visible_rect().width(),
video_frame->visible_rect().height());
break;
case media::VideoFrame::YV12A:
// Since libyuv doesn't support YUVA, fallback to media, which is not ARM
// optimized.
// TODO(fbarchard, mtomasz): Use libyuv, then copy the alpha channel.
media::ConvertYUVAToARGB(
video_frame->data(media::VideoFrame::kYPlane) + y_offset,
video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
video_frame->data(media::VideoFrame::kAPlane),
static_cast<uint8*>(bitmap->getPixels()),
video_frame->visible_rect().width(),
video_frame->visible_rect().height(),
video_frame->stride(media::VideoFrame::kYPlane),
video_frame->stride(media::VideoFrame::kUPlane),
video_frame->stride(media::VideoFrame::kAPlane),
bitmap->rowBytes(),
media::YV12);
break;
case media::VideoFrame::YV24:
libyuv::I444ToARGB(
video_frame->data(media::VideoFrame::kYPlane) + y_offset,
video_frame->stride(media::VideoFrame::kYPlane),
video_frame->data(media::VideoFrame::kUPlane) + uv_offset,
video_frame->stride(media::VideoFrame::kUPlane),
video_frame->data(media::VideoFrame::kVPlane) + uv_offset,
video_frame->stride(media::VideoFrame::kVPlane),
static_cast<uint8*>(bitmap->getPixels()),
bitmap->rowBytes(),
video_frame->visible_rect().width(),
video_frame->visible_rect().height());
#if SK_R32_SHIFT == 0 && SK_G32_SHIFT == 8 && SK_B32_SHIFT == 16 && \
SK_A32_SHIFT == 24
libyuv::ARGBToABGR(
static_cast<uint8*>(bitmap->getPixels()),
bitmap->rowBytes(),
static_cast<uint8*>(bitmap->getPixels()),
bitmap->rowBytes(),
video_frame->visible_rect().width(),
video_frame->visible_rect().height());
#endif
break;
case media::VideoFrame::NATIVE_TEXTURE:
DCHECK_EQ(video_frame->format(), media::VideoFrame::NATIVE_TEXTURE);
video_frame->ReadPixelsFromNativeTexture(*bitmap);
break;
default:
NOTREACHED();
break;
}
bitmap->notifyPixelsChanged();
bitmap->unlockPixels();
}
SkCanvasVideoRenderer::SkCanvasVideoRenderer()
: last_frame_timestamp_(media::kNoTimestamp()) {
}
SkCanvasVideoRenderer::~SkCanvasVideoRenderer() {}
void SkCanvasVideoRenderer::Paint(media::VideoFrame* video_frame,
SkCanvas* canvas,
const gfx::RectF& dest_rect,
uint8 alpha,
SkXfermode::Mode mode,
VideoRotation video_rotation) {
if (alpha == 0) {
return;
}
SkRect dest;
dest.set(dest_rect.x(), dest_rect.y(), dest_rect.right(), dest_rect.bottom());
SkPaint paint;
paint.setAlpha(alpha);
// Paint black rectangle if there isn't a frame available or the
// frame has an unexpected format.
if (!video_frame || !IsYUVOrNative(video_frame->format())) {
canvas->drawRect(dest, paint);
return;
}
// Check if we should convert and update |last_frame_|.
if (last_frame_.isNull() ||
video_frame->timestamp() != last_frame_timestamp_) {
ConvertVideoFrameToBitmap(video_frame, &last_frame_);
switch (video_rotation) {
case VIDEO_ROTATION_0:
break;
case VIDEO_ROTATION_90:
last_frame_ = SkBitmapOperations::Rotate(
last_frame_, SkBitmapOperations::ROTATION_90_CW);
break;
case VIDEO_ROTATION_180:
last_frame_ = SkBitmapOperations::Rotate(
last_frame_, SkBitmapOperations::ROTATION_180_CW);
break;
case VIDEO_ROTATION_270:
last_frame_ = SkBitmapOperations::Rotate(
last_frame_, SkBitmapOperations::ROTATION_270_CW);
break;
}
last_frame_timestamp_ = video_frame->timestamp();
}
paint.setXfermodeMode(mode);
// Paint using |last_frame_|.
paint.setFilterLevel(SkPaint::kLow_FilterLevel);
canvas->drawBitmapRect(last_frame_, NULL, dest, &paint);
}
void SkCanvasVideoRenderer::Copy(media::VideoFrame* video_frame,
SkCanvas* canvas) {
Paint(video_frame,
canvas,
video_frame->visible_rect(),
0xff,
SkXfermode::kSrc_Mode,
media::VIDEO_ROTATION_0);
}
} // namespace media