blob: 42369e89d3b8a2d483dff2eaee8683250149e03e [file] [log] [blame]
// Copyright (c) 2011 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 "remoting/base/encoder_vp8.h"
#include "base/logging.h"
#include "base/sys_info.h"
#include "media/base/yuv_convert.h"
#include "remoting/base/capture_data.h"
#include "remoting/base/util.h"
#include "remoting/proto/video.pb.h"
#include "third_party/skia/include/core/SkRegion.h"
extern "C" {
#define VPX_CODEC_DISABLE_COMPAT 1
#include "third_party/libvpx/libvpx.h"
}
namespace {
// Defines the dimension of a macro block. This is used to compute the active
// map for the encoder.
const int kMacroBlockSize = 16;
} // namespace remoting
namespace remoting {
EncoderVp8::EncoderVp8()
: initialized_(false),
codec_(NULL),
image_(NULL),
active_map_width_(0),
active_map_height_(0),
last_timestamp_(0),
size_(SkISize::Make(0, 0)) {
}
EncoderVp8::~EncoderVp8() {
Destroy();
}
void EncoderVp8::Destroy() {
if (initialized_) {
vpx_codec_err_t ret = vpx_codec_destroy(codec_.get());
DCHECK(ret == VPX_CODEC_OK) << "Failed to destroy codec";
initialized_ = false;
}
}
bool EncoderVp8::Init(const SkISize& size) {
Destroy();
size_ = size;
codec_.reset(new vpx_codec_ctx_t());
image_.reset(new vpx_image_t());
memset(image_.get(), 0, sizeof(vpx_image_t));
image_->fmt = VPX_IMG_FMT_YV12;
// libvpx seems to require both to be assigned.
image_->d_w = size.width();
image_->w = size.width();
image_->d_h = size.height();
image_->h = size.height();
// YUV image size is 1.5 times of a plane. Multiplication is performed first
// to avoid rounding error.
const int plane_size = size.width() * size.height();
const int yuv_image_size = plane_size * 3 / 2;
yuv_image_.reset(new uint8[yuv_image_size]);
// Reset image value to 128 so we just need to fill in the y plane.
memset(yuv_image_.get(), 128, yuv_image_size);
// Fill in the information for |image_|.
unsigned char* image = reinterpret_cast<unsigned char*>(yuv_image_.get());
image_->planes[0] = image;
image_->planes[1] = image + plane_size;
// The V plane starts from 1.25 of the plane size.
image_->planes[2] = image + plane_size + plane_size / 4;
// In YV12 Y plane has full width, UV plane has half width because of
// subsampling.
image_->stride[0] = image_->w;
image_->stride[1] = image_->w / 2;
image_->stride[2] = image_->w / 2;
vpx_codec_enc_cfg_t config;
const vpx_codec_iface_t* algo = vpx_codec_vp8_cx();
CHECK(algo);
vpx_codec_err_t ret = vpx_codec_enc_config_default(algo, &config, 0);
if (ret != VPX_CODEC_OK)
return false;
// Initialize active map.
active_map_width_ = (size.width() + kMacroBlockSize - 1) / kMacroBlockSize;
active_map_height_ = (size.height() + kMacroBlockSize - 1) / kMacroBlockSize;
active_map_.reset(new uint8[active_map_width_ * active_map_height_]);
config.rc_target_bitrate = size.width() * size.height() *
config.rc_target_bitrate / config.g_w / config.g_h;
config.g_w = size.width();
config.g_h = size.height();
config.g_pass = VPX_RC_ONE_PASS;
// Value of 2 means using the real time profile. This is basically a
// redundant option since we explicitly select real time mode when doing
// encoding.
config.g_profile = 2;
// Using 2 threads gives a great boost in performance for most systems with
// adequate processing power. NB: Going to multiple threads on low end
// windows systems can really hurt performance.
// http://crbug.com/99179
config.g_threads = (base::SysInfo::NumberOfProcessors() > 2) ? 2 : 1;
config.rc_min_quantizer = 20;
config.rc_max_quantizer = 30;
config.g_timebase.num = 1;
config.g_timebase.den = 20;
if (vpx_codec_enc_init(codec_.get(), algo, &config, 0))
return false;
// Value of 16 will have the smallest CPU load. This turns off subpixel
// motion search.
if (vpx_codec_control(codec_.get(), VP8E_SET_CPUUSED, 16))
return false;
// Use the lowest level of noise sensitivity so as to spend less time
// on motion estimation and inter-prediction mode.
if (vpx_codec_control(codec_.get(), VP8E_SET_NOISE_SENSITIVITY, 0))
return false;
return true;
}
// static
SkIRect EncoderVp8::AlignAndClipRect(const SkIRect& rect,
int width, int height) {
SkIRect screen(SkIRect::MakeWH(RoundToTwosMultiple(width),
RoundToTwosMultiple(height)));
if (!screen.intersect(AlignRect(rect))) {
screen = SkIRect::MakeWH(0, 0);
}
return screen;
}
bool EncoderVp8::PrepareImage(scoped_refptr<CaptureData> capture_data,
RectVector* updated_rects) {
// Perform RGB->YUV conversion.
if (capture_data->pixel_format() != media::VideoFrame::RGB32) {
LOG(ERROR) << "Only RGB32 is supported";
return false;
}
const SkRegion& region = capture_data->dirty_region();
const uint8* in = capture_data->data_planes().data[0];
const int in_stride = capture_data->data_planes().strides[0];
const int plane_size =
capture_data->size().width() * capture_data->size().height();
uint8* y_out = yuv_image_.get();
uint8* u_out = yuv_image_.get() + plane_size;
uint8* v_out = yuv_image_.get() + plane_size + plane_size / 4;
const int y_stride = image_->stride[0];
const int uv_stride = image_->stride[1];
DCHECK(updated_rects->empty());
for (SkRegion::Iterator r(region); !r.done(); r.next()) {
// Align the rectangle, report it as updated.
SkIRect rect = r.rect();
rect = AlignAndClipRect(rect, image_->w, image_->h);
if (!rect.isEmpty())
updated_rects->push_back(rect);
ConvertRGB32ToYUVWithRect(in,
y_out,
u_out,
v_out,
rect.fLeft,
rect.fTop,
rect.width(),
rect.height(),
in_stride,
y_stride,
uv_stride);
}
return true;
}
void EncoderVp8::PrepareActiveMap(const RectVector& updated_rects) {
// Clear active map first.
memset(active_map_.get(), 0, active_map_width_ * active_map_height_);
// Mark blocks at active.
for (size_t i = 0; i < updated_rects.size(); ++i) {
const SkIRect& r = updated_rects[i];
CHECK(r.width() && r.height());
int left = r.fLeft / kMacroBlockSize;
int right = (r.fRight - 1) / kMacroBlockSize;
int top = r.fTop / kMacroBlockSize;
int bottom = (r.fBottom - 1) / kMacroBlockSize;
CHECK(right < active_map_width_);
CHECK(bottom < active_map_height_);
uint8* map = active_map_.get() + top * active_map_width_;
for (int y = top; y <= bottom; ++y) {
for (int x = left; x <= right; ++x)
map[x] = 1;
map += active_map_width_;
}
}
}
void EncoderVp8::Encode(scoped_refptr<CaptureData> capture_data,
bool key_frame,
DataAvailableCallback* data_available_callback) {
if (!initialized_ || (capture_data->size() != size_)) {
bool ret = Init(capture_data->size());
// TODO(hclam): Handle error better.
DCHECK(ret) << "Initialization of encoder failed";
initialized_ = ret;
}
RectVector updated_rects;
if (!PrepareImage(capture_data, &updated_rects)) {
NOTREACHED() << "Can't image data for encoding";
}
// Update active map based on updated rectangles.
PrepareActiveMap(updated_rects);
// Apply active map to the encoder.
vpx_active_map_t act_map;
act_map.rows = active_map_height_;
act_map.cols = active_map_width_;
act_map.active_map = active_map_.get();
if (vpx_codec_control(codec_.get(), VP8E_SET_ACTIVEMAP, &act_map)) {
LOG(ERROR) << "Unable to apply active map";
}
// Do the actual encoding.
vpx_codec_err_t ret = vpx_codec_encode(codec_.get(), image_.get(),
last_timestamp_,
1, 0, VPX_DL_REALTIME);
DCHECK_EQ(ret, VPX_CODEC_OK)
<< "Encoding error: " << vpx_codec_err_to_string(ret) << "\n"
<< "Details: " << vpx_codec_error(codec_.get()) << "\n"
<< vpx_codec_error_detail(codec_.get());
// TODO(hclam): Apply the proper timestamp here.
last_timestamp_ += 50;
// Read the encoded data.
vpx_codec_iter_t iter = NULL;
bool got_data = false;
// TODO(hclam): Make sure we get exactly one frame from the packet.
// TODO(hclam): We should provide the output buffer to avoid one copy.
VideoPacket* message = new VideoPacket();
while (!got_data) {
const vpx_codec_cx_pkt_t* packet = vpx_codec_get_cx_data(codec_.get(),
&iter);
if (!packet)
continue;
switch (packet->kind) {
case VPX_CODEC_CX_FRAME_PKT:
got_data = true;
// TODO(sergeyu): Split each frame into multiple partitions.
message->set_data(packet->data.frame.buf, packet->data.frame.sz);
break;
default:
break;
}
}
message->mutable_format()->set_encoding(VideoPacketFormat::ENCODING_VP8);
message->set_flags(VideoPacket::FIRST_PACKET | VideoPacket::LAST_PACKET |
VideoPacket::LAST_PARTITION);
message->mutable_format()->set_screen_width(capture_data->size().width());
message->mutable_format()->set_screen_height(capture_data->size().height());
message->set_capture_time_ms(capture_data->capture_time_ms());
message->set_client_sequence_number(capture_data->client_sequence_number());
for (size_t i = 0; i < updated_rects.size(); ++i) {
Rect* rect = message->add_dirty_rects();
rect->set_x(updated_rects[i].fLeft);
rect->set_y(updated_rects[i].fTop);
rect->set_width(updated_rects[i].width());
rect->set_height(updated_rects[i].height());
}
data_available_callback->Run(message);
delete data_available_callback;
}
} // namespace remoting