| // Copyright 2013 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/codec/video_decoder_vpx.h" |
| |
| #include <math.h> |
| #include <stdint.h> |
| |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "remoting/base/util.h" |
| #include "remoting/proto/video.pb.h" |
| #include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" |
| #include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" |
| #include "third_party/libyuv/include/libyuv/convert_argb.h" |
| #include "third_party/libyuv/include/libyuv/convert_from.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_region.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| void RenderRect(vpx_image_t* image, |
| webrtc::DesktopRect rect, |
| VideoDecoder::PixelFormat pixel_format, |
| webrtc::DesktopFrame* frame) { |
| auto yuv_to_rgb_function = libyuv::I420ToARGB; |
| int u_offset; |
| int v_offset; |
| |
| switch (image->fmt) { |
| case VPX_IMG_FMT_I420: { |
| // Align position of the top left corner so that its coordinates are |
| // always even. |
| rect = webrtc::DesktopRect::MakeLTRB(rect.left() & ~1, rect.top() & ~1, |
| rect.right(), rect.bottom()); |
| u_offset = rect.top() / 2 * image->stride[1] + rect.left() / 2; |
| v_offset = rect.top() / 2 * image->stride[2] + rect.left() / 2; |
| yuv_to_rgb_function = (pixel_format == VideoDecoder::PixelFormat::BGRA) |
| ? libyuv::I420ToARGB |
| : libyuv::I420ToABGR; |
| break; |
| } |
| // VP8 only outputs I420 frames, but VP9 can also produce I444. |
| case VPX_IMG_FMT_I444: { |
| u_offset = rect.top() * image->stride[1] + rect.left(); |
| v_offset = rect.top() * image->stride[2] + rect.left(); |
| yuv_to_rgb_function = (pixel_format == VideoDecoder::PixelFormat::BGRA) |
| ? libyuv::I444ToARGB |
| : libyuv::I444ToABGR; |
| break; |
| } |
| default: { |
| LOG(ERROR) << "Unsupported image format:" << image->fmt; |
| return; |
| } |
| } |
| |
| int y_offset = rect.top() * image->stride[0] + rect.left(); |
| uint8_t* image_data_ptr = frame->GetFrameDataAtPos(rect.top_left()); |
| yuv_to_rgb_function(image->planes[0] + y_offset, image->stride[0], |
| image->planes[1] + u_offset, image->stride[1], |
| image->planes[2] + v_offset, image->stride[2], |
| image_data_ptr, frame->stride(), rect.width(), |
| rect.height()); |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<VideoDecoderVpx> VideoDecoderVpx::CreateForVP8() { |
| return base::WrapUnique(new VideoDecoderVpx(vpx_codec_vp8_dx())); |
| } |
| |
| // static |
| std::unique_ptr<VideoDecoderVpx> VideoDecoderVpx::CreateForVP9() { |
| return base::WrapUnique(new VideoDecoderVpx(vpx_codec_vp9_dx())); |
| } |
| |
| VideoDecoderVpx::~VideoDecoderVpx() = default; |
| |
| void VideoDecoderVpx::SetPixelFormat(PixelFormat pixel_format) { |
| pixel_format_ = pixel_format; |
| } |
| |
| bool VideoDecoderVpx::DecodePacket(const VideoPacket& packet, |
| webrtc::DesktopFrame* frame) { |
| // Pass the packet to the codec to process. |
| vpx_codec_err_t ret = vpx_codec_decode( |
| codec_.get(), reinterpret_cast<const uint8_t*>(packet.data().data()), |
| packet.data().size(), nullptr, 0); |
| if (ret != VPX_CODEC_OK) { |
| const char* error = vpx_codec_error(codec_.get()); |
| const char* error_detail = vpx_codec_error_detail(codec_.get()); |
| LOG(ERROR) << "Decoding failed:" << (error ? error : "(NULL)") << "\n" |
| << "Details: " << (error_detail ? error_detail : "(NULL)"); |
| return false; |
| } |
| |
| // Fetch the decoded video frame. |
| vpx_codec_iter_t iter = nullptr; |
| vpx_image_t* image = vpx_codec_get_frame(codec_.get(), &iter); |
| if (!image) { |
| LOG(ERROR) << "No video frame decoded."; |
| return false; |
| } |
| if (!webrtc::DesktopSize(image->d_w, image->d_h).equals(frame->size())) { |
| LOG(ERROR) << "Size of the encoded frame doesn't match size in the header."; |
| return false; |
| } |
| |
| // Determine which areas have been updated. |
| webrtc::DesktopRegion* region = frame->mutable_updated_region(); |
| region->Clear(); |
| for (int i = 0; i < packet.dirty_rects_size(); ++i) { |
| Rect proto_rect = packet.dirty_rects(i); |
| webrtc::DesktopRect rect = |
| webrtc::DesktopRect::MakeXYWH(proto_rect.x(), proto_rect.y(), |
| proto_rect.width(), proto_rect.height()); |
| region->AddRect(rect); |
| RenderRect(image, rect, pixel_format_, frame); |
| } |
| |
| return true; |
| } |
| |
| VideoDecoderVpx::VideoDecoderVpx(vpx_codec_iface_t* codec) { |
| codec_.reset(new vpx_codec_ctx_t); |
| |
| vpx_codec_dec_cfg config; |
| config.w = 0; |
| config.h = 0; |
| config.threads = 2; |
| vpx_codec_err_t ret = vpx_codec_dec_init(codec_.get(), codec, &config, 0); |
| CHECK_EQ(VPX_CODEC_OK, ret); |
| } |
| |
| } // namespace remoting |