// 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 "media/base/callback.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_(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 gfx::Size& 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(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 would give a great boost in performance in most systems // while hurting single core systems just a little bit. config.g_threads = 2; 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 gfx::Rect EncoderVp8::AlignAndClipRect(const gfx::Rect& rect, int width, int height) { gfx::Rect screen(RoundToTwosMultiple(width), RoundToTwosMultiple(height)); return screen.Intersect(AlignRect(rect)); } bool EncoderVp8::PrepareImage(scoped_refptr capture_data, std::vector* 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 skRect = r.rect(); gfx::Rect rect(skRect.fLeft, skRect.fTop, skRect.width(), skRect.height()); rect = AlignAndClipRect(rect, image_->w, image_->h); if (!rect.IsEmpty()) updated_rects->push_back(rect); ConvertRGB32ToYUVWithRect(in, y_out, u_out, v_out, rect.x(), rect.y(), rect.width(), rect.height(), in_stride, y_stride, uv_stride); } return true; } void EncoderVp8::PrepareActiveMap( const std::vector& 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 gfx::Rect& r = updated_rects[i]; CHECK(r.width() && r.height()); int left = r.x() / kMacroBlockSize; int right = (r.right() - 1) / kMacroBlockSize; int top = r.y() / kMacroBlockSize; int bottom = (r.bottom() - 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 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; } std::vector 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].x()); rect->set_y(updated_rects[i].y()); 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