/* Copyright 2017 The Chromium OS 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 "arc/cached_frame.h" #include #include #include "arc/common.h" namespace arc { using android::CameraMetadata; CachedFrame::CachedFrame() : source_frame_(nullptr), cropped_buffer_capacity_(0), yu12_frame_(new AllocatedFrameBuffer(0)), scaled_frame_(new AllocatedFrameBuffer(0)) {} CachedFrame::~CachedFrame() { UnsetSource(); } int CachedFrame::SetSource(const FrameBuffer* frame, int rotate_degree) { source_frame_ = frame; int res = ConvertToYU12(); if (res != 0) { return res; } if (rotate_degree > 0) { res = CropRotateScale(rotate_degree); } return res; } void CachedFrame::UnsetSource() { source_frame_ = nullptr; } uint8_t* CachedFrame::GetSourceBuffer() const { return source_frame_->GetData(); } size_t CachedFrame::GetSourceDataSize() const { return source_frame_->GetDataSize(); } uint32_t CachedFrame::GetSourceFourCC() const { return source_frame_->GetFourcc(); } uint8_t* CachedFrame::GetCachedBuffer() const { return yu12_frame_->GetData(); } uint32_t CachedFrame::GetCachedFourCC() const { return yu12_frame_->GetFourcc(); } uint32_t CachedFrame::GetWidth() const { return yu12_frame_->GetWidth(); } uint32_t CachedFrame::GetHeight() const { return yu12_frame_->GetHeight(); } size_t CachedFrame::GetConvertedSize(int fourcc) const { return ImageProcessor::GetConvertedSize(fourcc, yu12_frame_->GetWidth(), yu12_frame_->GetHeight()); } int CachedFrame::Convert(const CameraMetadata& metadata, FrameBuffer* out_frame, bool video_hack) { if (video_hack && out_frame->GetFourcc() == V4L2_PIX_FMT_YVU420) { out_frame->SetFourcc(V4L2_PIX_FMT_YUV420); } FrameBuffer* source_frame = yu12_frame_.get(); if (GetWidth() != out_frame->GetWidth() || GetHeight() != out_frame->GetHeight()) { size_t cache_size = ImageProcessor::GetConvertedSize( yu12_frame_->GetFourcc(), out_frame->GetWidth(), out_frame->GetHeight()); if (cache_size == 0) { return -EINVAL; } else if (cache_size > scaled_frame_->GetBufferSize()) { scaled_frame_.reset(new AllocatedFrameBuffer(cache_size)); } scaled_frame_->SetWidth(out_frame->GetWidth()); scaled_frame_->SetHeight(out_frame->GetHeight()); ImageProcessor::Scale(*yu12_frame_.get(), scaled_frame_.get()); source_frame = scaled_frame_.get(); } return ImageProcessor::ConvertFormat(metadata, *source_frame, out_frame); } int CachedFrame::ConvertToYU12() { size_t cache_size = ImageProcessor::GetConvertedSize( V4L2_PIX_FMT_YUV420, source_frame_->GetWidth(), source_frame_->GetHeight()); if (cache_size == 0) { return -EINVAL; } yu12_frame_->SetDataSize(cache_size); yu12_frame_->SetFourcc(V4L2_PIX_FMT_YUV420); yu12_frame_->SetWidth(source_frame_->GetWidth()); yu12_frame_->SetHeight(source_frame_->GetHeight()); int res = ImageProcessor::ConvertFormat(CameraMetadata(), *source_frame_, yu12_frame_.get()); if (res) { LOGF(ERROR) << "Convert from " << FormatToString(source_frame_->GetFourcc()) << " to YU12 fails."; return res; } return 0; } int CachedFrame::CropRotateScale(int rotate_degree) { // TODO(henryhsu): Move libyuv part to ImageProcessor. if (yu12_frame_->GetHeight() % 2 != 0 || yu12_frame_->GetWidth() % 2 != 0) { LOGF(ERROR) << "yu12_frame_ has odd dimension: " << yu12_frame_->GetWidth() << "x" << yu12_frame_->GetHeight(); return -EINVAL; } if (yu12_frame_->GetHeight() > yu12_frame_->GetWidth()) { LOGF(ERROR) << "yu12_frame_ is tall frame already: " << yu12_frame_->GetWidth() << "x" << yu12_frame_->GetHeight(); return -EINVAL; } // Step 1: Crop and rotate // // Original frame Cropped frame Rotated frame // -------------------- -------- // | | | | | | --------------- // | | | | | | | | // | | | | =======>> | | =======>> | | // | | | | | | --------------- // | | | | | | // -------------------- -------- // int cropped_width = yu12_frame_->GetHeight() * yu12_frame_->GetHeight() / yu12_frame_->GetWidth(); if (cropped_width % 2 == 1) { // Make cropped_width to the closest even number. cropped_width++; } int cropped_height = yu12_frame_->GetHeight(); int margin = (yu12_frame_->GetWidth() - cropped_width) / 2; int rotated_height = cropped_width; int rotated_width = cropped_height; int rotated_y_stride = rotated_width; int rotated_uv_stride = rotated_width / 2; size_t rotated_size = rotated_y_stride * rotated_height + rotated_uv_stride * rotated_height; if (rotated_size > cropped_buffer_capacity_) { cropped_buffer_.reset(new uint8_t[rotated_size]); cropped_buffer_capacity_ = rotated_size; } uint8_t* rotated_y_plane = cropped_buffer_.get(); uint8_t* rotated_u_plane = rotated_y_plane + rotated_y_stride * rotated_height; uint8_t* rotated_v_plane = rotated_u_plane + rotated_uv_stride * rotated_height / 2; libyuv::RotationMode rotation_mode = libyuv::RotationMode::kRotate90; switch (rotate_degree) { case 90: rotation_mode = libyuv::RotationMode::kRotate90; break; case 270: rotation_mode = libyuv::RotationMode::kRotate270; break; default: LOGF(ERROR) << "Invalid rotation degree: " << rotate_degree; return -EINVAL; } // This libyuv method first crops the frame and then rotates it 90 degrees // clockwise. int res = libyuv::ConvertToI420( yu12_frame_->GetData(), yu12_frame_->GetDataSize(), rotated_y_plane, rotated_y_stride, rotated_u_plane, rotated_uv_stride, rotated_v_plane, rotated_uv_stride, margin, 0, yu12_frame_->GetWidth(), yu12_frame_->GetHeight(), cropped_width, cropped_height, rotation_mode, libyuv::FourCC::FOURCC_I420); if (res) { LOGF(ERROR) << "ConvertToI420 failed: " << res; return res; } // Step 2: Scale // // Final frame // Rotated frame --------------------- // -------------- | | // | | =====>> | | // | | | | // -------------- | | // | | // --------------------- // // res = libyuv::I420Scale( rotated_y_plane, rotated_y_stride, rotated_u_plane, rotated_uv_stride, rotated_v_plane, rotated_uv_stride, rotated_width, rotated_height, yu12_frame_->GetData(), yu12_frame_->GetWidth(), yu12_frame_->GetData() + yu12_frame_->GetWidth() * yu12_frame_->GetHeight(), yu12_frame_->GetWidth() / 2, yu12_frame_->GetData() + yu12_frame_->GetWidth() * yu12_frame_->GetHeight() * 5 / 4, yu12_frame_->GetWidth() / 2, yu12_frame_->GetWidth(), yu12_frame_->GetHeight(), libyuv::FilterMode::kFilterNone); LOGF_IF(ERROR, res) << "I420Scale failed: " << res; return res; } } // namespace arc