| /* 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 "hal/usb/cached_frame.h" |
| |
| #include <errno.h> |
| |
| #include <limits> |
| #include <string> |
| |
| #include <base/check_op.h> |
| #include <base/timer/elapsed_timer.h> |
| #include <hardware/camera3.h> |
| |
| #include "cros-camera/common.h" |
| #include "cros-camera/exif_utils.h" |
| #include "cros-camera/utils/camera_config.h" |
| #include "hal/usb/camera_hal.h" |
| #include "hal/usb/common_types.h" |
| |
| namespace cros { |
| |
| // How precise the float-to-rational conversion for EXIF tags would be. |
| static const int kRationalPrecision = 10000; |
| |
| // Since the structure of the App1 segment is like: |
| // 0xFF [1 byte marker] [2 bytes size] [data] |
| // And it should not be larger than 64K. |
| static const int kApp1MaxDataSize = 65532; |
| |
| static bool SetExifTags(const android::CameraMetadata& static_metadata, |
| const android::CameraMetadata& request_metadata, |
| const FrameBuffer& in_frame, |
| ExifUtils* utils); |
| |
| static void InsertJpegBlob(FrameBuffer* out_frame, uint32_t jpeg_data_size); |
| |
| static Size CalculateCropSize(const Size& in_size, const Size& out_size) { |
| // Crop the input image to the same ratio as the output image. |
| // We want to compare w1/h1 and w2/h2. To avoid floating point precision loss |
| // we compare w1*h2 and w2*h1 instead, with w1 and h1 being the width and |
| // height of the input; w2 and h2 those of the output. |
| uint32_t in_aspect_ratio = in_size.width * out_size.height; |
| uint32_t out_aspect_ratio = out_size.width * in_size.height; |
| |
| // Same Ratio. |
| Size crop_size(0u, 0u); |
| if (in_aspect_ratio == out_aspect_ratio) { |
| crop_size.width = in_size.width; |
| crop_size.height = in_size.height; |
| } else if (in_aspect_ratio > out_aspect_ratio) { |
| // Need to crop width. |
| crop_size.width = out_aspect_ratio / out_size.height; |
| crop_size.height = in_size.height; |
| } else { |
| // Need to crop height. |
| crop_size.width = in_size.width; |
| crop_size.height = in_aspect_ratio / out_size.width; |
| } |
| // Make sure crop size is even. |
| crop_size.width = (crop_size.width + 1) & (~1); |
| crop_size.height = (crop_size.height + 1) & (~1); |
| |
| return crop_size; |
| } |
| |
| static bool ValidateThumbnailSize( |
| const android::CameraMetadata& static_metadata, int width, int height) { |
| auto entry = static_metadata.find(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES); |
| DCHECK_GT(entry.count, 0); |
| DCHECK_EQ(entry.count % 2, 0); |
| for (size_t i = 0; i < entry.count; i += 2) { |
| if (width == entry.data.i32[i] && height == entry.data.i32[i + 1]) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| CachedFrame::CachedFrame(const android::CameraMetadata& static_metadata) |
| : image_processor_(new ImageProcessor()), |
| camera_metrics_(CameraMetrics::New()), |
| jda_available_(false), |
| jda_resolution_cap_(std::numeric_limits<int>::max(), |
| std::numeric_limits<int>::max()), |
| force_jpeg_hw_decode_(false) { |
| auto* mojo_manager_token = CameraHal::GetInstance().GetMojoManagerToken(); |
| |
| jda_ = JpegDecodeAccelerator::CreateInstance(mojo_manager_token); |
| jda_available_ = jda_->Start(); |
| if (!jda_available_) { |
| LOGF(INFO) << "No JDA available"; |
| } else { |
| std::unique_ptr<CameraConfig> camera_config = |
| CameraConfig::Create(constants::kCrosCameraConfigPathString); |
| jda_resolution_cap_ = |
| Size(camera_config->GetInteger(constants::kCrosUsbJDACapWidth, |
| std::numeric_limits<int>::max()), |
| camera_config->GetInteger(constants::kCrosUsbJDACapHeight, |
| std::numeric_limits<int>::max())); |
| if (jda_resolution_cap_.width != std::numeric_limits<int>::max() || |
| jda_resolution_cap_.height != std::numeric_limits<int>::max()) { |
| LOGF(INFO) << "Cap JDA resolution:" << jda_resolution_cap_.width << "x" |
| << jda_resolution_cap_.height; |
| } |
| } |
| |
| jpeg_compressor_ = JpegCompressor::GetInstance(mojo_manager_token); |
| |
| // Read force_jpeg_hw_dec configs |
| std::unique_ptr<CameraConfig> camera_config = |
| CameraConfig::Create(constants::kCrosCameraTestConfigPathString); |
| force_jpeg_hw_decode_ = camera_config->GetBoolean( |
| constants::kCrosForceJpegHardwareDecodeOption, false); |
| if (force_jpeg_hw_decode_) { |
| LOGF(INFO) << "Force JPEG hardware decode"; |
| } |
| |
| camera_metadata_ro_entry entry = |
| static_metadata.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); |
| DCHECK_EQ(entry.count, 4); |
| active_array_size_.width = entry.data.i32[2]; |
| active_array_size_.height = entry.data.i32[3]; |
| |
| face_detector_ = FaceDetector::Create(); |
| } |
| |
| int CachedFrame::Convert( |
| const android::CameraMetadata& static_metadata, |
| const android::CameraMetadata& request_metadata, |
| int rotate_degree, |
| const FrameBuffer& in_frame, |
| const std::vector<std::unique_ptr<FrameBuffer>>& out_frames, |
| std::vector<int>* out_frame_status, |
| std::vector<human_sensing::CrosFace>* faces) { |
| // |
| // Overview of the conversion graph. |
| // |
| // 1. Convert input (MJPEG or YUYV) into a temp NV12 buffer, which is taken |
| // from output buffers if a suitable size/format is found, or is allocated |
| // and cached in this class. |
| // |
| // 1a. Input format is MJPEG. Try HW JDA decoding or fallback to SW |
| // decoding: |
| // |
| // HW JDA |
| // MJPEG -------------------------------> NV12 |
| // \ SW Decode Convert / |
| // ------------> I420 ----------- |
| // |
| // 1b. Input format is YUYV: |
| // |
| // Convert |
| // YUYV ----------> NV12 |
| // |
| // 2. If source frame needs to be rotated for portrait view, the temp NV12 |
| // buffer will be cropped, rotated, and scaled back into it: |
| // |
| // Crop + Rotate Scale Convert |
| // NV12 ----------------> I420 --------> I420' ----------> NV12 |
| // |
| // 3. Convert the temp NV12 frame into each output frame. |
| // |
| // 3a. Output size is the same as input size: |
| // |
| // HW/SW JEA |
| // NV12 ( ------------> JPEG ) |
| // Convert |
| // ( ------------> other ) |
| // |
| // 3b. Input size needs to be cropped and scaled into output size: |
| // |
| // Crop Scale Convert HW/SW JEA |
| // NV12 -----> I420 ------> I420' ( --------> NV12' ----------> JPEG ) |
| // Convert |
| // ( --------> other ) |
| // |
| // TODO(kamesan): optimize the SW paths to reduce I420 <-> NV12 copies, by |
| // refactoring of the graph or libyuv support. |
| // |
| VLOGF(1) << "Input frame: " << in_frame.GetWidth() << "x" |
| << in_frame.GetHeight() << " " |
| << FormatToString(in_frame.GetFourcc()) << ", rotate " |
| << rotate_degree; |
| |
| if (in_frame.GetHeight() % 2 != 0 || in_frame.GetWidth() % 2 != 0) { |
| LOGF(ERROR) << "Source image has odd dimension: " << in_frame.GetWidth() |
| << "x" << in_frame.GetHeight(); |
| return -EINVAL; |
| } |
| |
| // Try to find a temp NV12 buffer in |out_frames| that has the same size as |
| // |in_frame|. If found, |in_frame| will be converted directly into it to save |
| // a copy. |
| size_t nv12_frame_index = out_frames.size(); |
| for (size_t i = 0; i < out_frames.size(); i++) { |
| FrameBuffer* out_frame = out_frames[i].get(); |
| VLOGF(1) << "Output frame " << i << ": " << out_frame->GetWidth() << "x" |
| << out_frame->GetHeight() << " " |
| << FormatToString(out_frame->GetFourcc()); |
| if (in_frame.GetWidth() == out_frame->GetWidth() && |
| in_frame.GetHeight() == out_frame->GetHeight() && |
| (out_frame->GetFourcc() == V4L2_PIX_FMT_NV12 || |
| out_frame->GetFourcc() == V4L2_PIX_FMT_NV12M)) { |
| nv12_frame_index = i; |
| break; |
| } |
| } |
| |
| FrameBuffer* nv12_frame; |
| if (nv12_frame_index < out_frames.size()) { |
| nv12_frame = out_frames[nv12_frame_index].get(); |
| } else { |
| if (!GrallocFrameBuffer::Reallocate(in_frame.GetWidth(), |
| in_frame.GetHeight(), V4L2_PIX_FMT_NV12, |
| &temp_nv12_frame_)) { |
| return -EINVAL; |
| } |
| nv12_frame = temp_nv12_frame_.get(); |
| } |
| |
| // Convert |in_frame| into |nv12_frame|. |
| if (in_frame.GetFourcc() == V4L2_PIX_FMT_MJPEG) { |
| int ret = DecodeToNV12(in_frame, nv12_frame); |
| if (ret) |
| return ret; |
| } else { |
| if (nv12_frame->Map()) { |
| LOGF(ERROR) << "Failed to map frame"; |
| return -EINVAL; |
| } |
| int ret = image_processor_->ConvertFormat(in_frame, nv12_frame); |
| if (ret) |
| return ret; |
| } |
| |
| if (rotate_degree > 0) { |
| int ret = CropRotateScale(rotate_degree, nv12_frame); |
| if (ret) |
| return ret; |
| } |
| |
| if (faces && request_metadata.exists(ANDROID_STATISTICS_FACE_DETECT_MODE)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_STATISTICS_FACE_DETECT_MODE); |
| if (entry.data.u8[0] == ANDROID_STATISTICS_FACE_DETECT_MODE_SIMPLE) { |
| DetectFaces(*nv12_frame, faces); |
| } else { |
| faces->clear(); |
| } |
| } |
| |
| // Convert |nv12_frame| into the output frames. At this time, this |
| // function will always return 0 and record the per-output-frame conversion |
| // status in |out_frame_status|. |
| out_frame_status->resize(out_frames.size()); |
| for (size_t i = 0; i < out_frames.size(); i++) { |
| if (i == nv12_frame_index) { |
| (*out_frame_status)[i] = 0; |
| continue; |
| } |
| if (out_frames[i]->Map()) { |
| LOGF(ERROR) << "Failed to map frame"; |
| (*out_frame_status)[i] = -EINVAL; |
| continue; |
| } |
| (*out_frame_status)[i] = ConvertFromNV12(static_metadata, request_metadata, |
| *nv12_frame, out_frames[i].get()); |
| } |
| return 0; |
| } |
| |
| int CachedFrame::ConvertFromNV12( |
| const android::CameraMetadata& static_metadata, |
| const android::CameraMetadata& request_metadata, |
| const FrameBuffer& in_frame, |
| FrameBuffer* out_frame) { |
| const Size in_size(in_frame.GetWidth(), in_frame.GetHeight()); |
| const Size out_size(out_frame->GetWidth(), out_frame->GetHeight()); |
| const Size crop_size = CalculateCropSize(in_size, out_size); |
| |
| const FrameBuffer* src_frame = &in_frame; |
| if (!(in_size == out_size)) { |
| // Crop to the same aspect ratio of output size. Also converts format to |
| // I420 since libyuv doesn't support NV12 scaling. |
| if (!SharedFrameBuffer::Reallocate(crop_size.width, crop_size.height, |
| V4L2_PIX_FMT_YUV420, |
| &temp_i420_frame_)) { |
| return -EINVAL; |
| } |
| int ret = image_processor_->Crop(in_frame, temp_i420_frame_.get()); |
| if (ret) |
| return ret; |
| // Scale to the output size. |
| if (!SharedFrameBuffer::Reallocate( |
| out_frame->GetWidth(), out_frame->GetHeight(), V4L2_PIX_FMT_YUV420, |
| &temp_i420_frame2_)) { |
| return -EINVAL; |
| } |
| ret = image_processor_->Scale(*temp_i420_frame_, temp_i420_frame2_.get()); |
| if (ret) |
| return ret; |
| src_frame = temp_i420_frame2_.get(); |
| } |
| |
| // Output JPEG. |
| if (out_frame->GetFourcc() == V4L2_PIX_FMT_JPEG) { |
| if (src_frame->GetFourcc() != V4L2_PIX_FMT_NV12 && |
| src_frame->GetFourcc() != V4L2_PIX_FMT_NV12M) { |
| if (!GrallocFrameBuffer::Reallocate( |
| out_frame->GetWidth(), out_frame->GetHeight(), V4L2_PIX_FMT_NV12, |
| &temp_nv12_frame2_)) { |
| return -EINVAL; |
| } |
| if (temp_nv12_frame2_->Map()) { |
| LOGF(ERROR) << "Failed to map frame"; |
| return -EINVAL; |
| } |
| int ret = |
| image_processor_->ConvertFormat(*src_frame, temp_nv12_frame2_.get()); |
| if (ret) |
| return ret; |
| src_frame = temp_nv12_frame2_.get(); |
| } |
| return CompressNV12(static_metadata, request_metadata, *src_frame, |
| out_frame); |
| } |
| // Output other formats. |
| return image_processor_->ConvertFormat(*src_frame, out_frame); |
| } |
| |
| int CachedFrame::DecodeToNV12(const FrameBuffer& in_frame, |
| FrameBuffer* out_frame) { |
| // Try HW decoding. |
| base::ElapsedTimer hw_timer; |
| int ret = DecodeByJDA(in_frame, out_frame); |
| if (ret == 0) { |
| camera_metrics_->SendJpegProcessLatency(JpegProcessType::kDecode, |
| JpegProcessMethod::kHardware, |
| hw_timer.Elapsed()); |
| camera_metrics_->SendJpegResolution( |
| JpegProcessType::kDecode, JpegProcessMethod::kHardware, |
| in_frame.GetWidth(), in_frame.GetHeight()); |
| return ret; |
| } |
| if (ret == -EAGAIN) { |
| return ret; |
| } |
| |
| // JDA error, fallback to SW decoding if not forcing HW decoding. |
| if (force_jpeg_hw_decode_) { |
| return -EINVAL; |
| } |
| |
| // SW decoding. If this fails, the input frame is likely invalid. Return |
| // -EAGAIN so HAL can skip this frame. |
| if (out_frame->Map()) { |
| LOGF(ERROR) << "Failed to map frame"; |
| return -EINVAL; |
| } |
| if (!SharedFrameBuffer::Reallocate(in_frame.GetWidth(), in_frame.GetHeight(), |
| V4L2_PIX_FMT_YUV420, &temp_i420_frame_)) { |
| return -EINVAL; |
| } |
| base::ElapsedTimer sw_timer; |
| ret = image_processor_->ConvertFormat(in_frame, temp_i420_frame_.get()); |
| if (ret) { |
| LOGF(ERROR) << "Decode JPEG to YU12 failed: " << ret; |
| return -EAGAIN; |
| } |
| ret = image_processor_->ConvertFormat(*temp_i420_frame_, out_frame); |
| if (ret) { |
| return -EINVAL; |
| } |
| camera_metrics_->SendJpegProcessLatency(JpegProcessType::kDecode, |
| JpegProcessMethod::kSoftware, |
| sw_timer.Elapsed()); |
| camera_metrics_->SendJpegResolution( |
| JpegProcessType::kDecode, JpegProcessMethod::kSoftware, |
| in_frame.GetWidth(), in_frame.GetHeight()); |
| return 0; |
| } |
| |
| int CachedFrame::DecodeByJDA(const FrameBuffer& in_frame, |
| FrameBuffer* out_frame) { |
| if (!jda_available_) |
| return -EINVAL; |
| |
| // For test case, don't need to cap the resolution. |
| if (!force_jpeg_hw_decode_) { |
| if (in_frame.GetWidth() > jda_resolution_cap_.width || |
| in_frame.GetHeight() > jda_resolution_cap_.height) { |
| return -EINVAL; |
| } |
| } |
| // The output frame needs to be mapped after HW JDA processing otherwise the |
| // mapped addresses won't sync the content correctly. If the frame is already |
| // mapped, unmap it first. |
| // TODO(kamesan): simplify the map/unmap logics when we figure out a proper |
| // way to sync mapped addresses (probably inside JDA implementation). |
| if (out_frame->Unmap()) { |
| LOGF(ERROR) << "Failed to unmap frame"; |
| return -EINVAL; |
| } |
| // TODO(kamesan): pass the buffer offset from V4L2 to here. |
| JpegDecodeAccelerator::Error error = jda_->DecodeSync( |
| in_frame.GetFd(), in_frame.GetDataSize(), 0 /* input_buffer_offset */, |
| out_frame->GetBufferHandle()); |
| switch (error) { |
| case JpegDecodeAccelerator::Error::NO_ERRORS: |
| if (out_frame->Map()) { |
| LOGF(ERROR) << "Failed to map frame"; |
| return -EINVAL; |
| } |
| return 0; |
| case JpegDecodeAccelerator::Error::PARSE_JPEG_FAILED: |
| case JpegDecodeAccelerator::Error::UNSUPPORTED_JPEG: |
| // Camera device may temporarily output invalid MJPEG stream. HAL should |
| // skip this frame and get the next frame. |
| return -EAGAIN; |
| case JpegDecodeAccelerator::Error::TRY_START_AGAIN: |
| // Possibly Mojo communication error. Try restart JDA and return error |
| // this time. |
| jda_available_ = jda_->Start(); |
| LOGF(WARNING) << "Restart JDA: " << jda_available_; |
| return -EINVAL; |
| default: |
| // Other JDA errors. |
| return -EINVAL; |
| } |
| } |
| |
| int CachedFrame::CropRotateScale(int rotate_degree, FrameBuffer* frame) { |
| if (frame->GetHeight() > frame->GetWidth()) { |
| LOGF(ERROR) << "frame is tall frame already: " << frame->GetWidth() << "x" |
| << frame->GetHeight(); |
| return -EINVAL; |
| } |
| |
| // Step 1: Crop and rotate |
| // |
| // Original frame Cropped frame Rotated frame |
| // -------------------- -------- |
| // | | | | | | --------------- |
| // | | | | | | | | |
| // | | | | =======>> | | =======>> | | |
| // | | | | | | --------------- |
| // | | | | | | |
| // -------------------- -------- |
| // |
| uint32_t cropped_width = |
| frame->GetHeight() * frame->GetHeight() / frame->GetWidth(); |
| if (cropped_width % 2 == 1) { |
| // Make cropped_width to the closest even number. |
| cropped_width++; |
| } |
| int cropped_height = frame->GetHeight(); |
| if (!SharedFrameBuffer::Reallocate(cropped_height, cropped_width, |
| V4L2_PIX_FMT_YUV420, &temp_i420_frame2_)) { |
| return -EINVAL; |
| } |
| |
| int ret = image_processor_->ProcessForInsetPortraitMode( |
| *frame, temp_i420_frame2_.get(), rotate_degree); |
| if (ret) { |
| LOGF(ERROR) << "Crop and Rotate " << rotate_degree << " degree fails."; |
| return ret; |
| } |
| |
| // Step 2: Scale |
| // |
| // Final frame |
| // Rotated frame --------------------- |
| // -------------- | | |
| // | | =====>> | | |
| // | | | | |
| // -------------- | | |
| // | | |
| // --------------------- |
| // |
| if (!SharedFrameBuffer::Reallocate(frame->GetWidth(), frame->GetHeight(), |
| V4L2_PIX_FMT_YUV420, &temp_i420_frame_)) { |
| return -EINVAL; |
| } |
| ret = image_processor_->Scale(*temp_i420_frame2_, temp_i420_frame_.get()); |
| LOGF_IF(ERROR, ret) << "Scale failed: " << ret; |
| |
| return image_processor_->ConvertFormat(*temp_i420_frame_, frame); |
| } |
| |
| int CachedFrame::CompressNV12(const android::CameraMetadata& static_metadata, |
| const android::CameraMetadata& request_metadata, |
| const FrameBuffer& in_frame, |
| FrameBuffer* out_frame) { |
| ExifUtils utils; |
| if (!utils.Initialize()) { |
| LOGF(ERROR) << "ExifUtils initialization failed."; |
| return -ENODEV; |
| } |
| |
| if (!SetExifTags(static_metadata, request_metadata, in_frame, &utils)) { |
| LOGF(ERROR) << "Setting Exif tags failed."; |
| return -EINVAL; |
| } |
| |
| int jpeg_quality, thumbnail_jpeg_quality; |
| camera_metadata_ro_entry entry = request_metadata.find(ANDROID_JPEG_QUALITY); |
| if (entry.count) { |
| jpeg_quality = entry.data.u8[0]; |
| } else { |
| LOGF(ERROR) << "Cannot find jpeg quality in metadata."; |
| return -EINVAL; |
| } |
| if (request_metadata.exists(ANDROID_JPEG_THUMBNAIL_QUALITY)) { |
| entry = request_metadata.find(ANDROID_JPEG_THUMBNAIL_QUALITY); |
| thumbnail_jpeg_quality = entry.data.u8[0]; |
| } else { |
| thumbnail_jpeg_quality = jpeg_quality; |
| } |
| |
| // Generate thumbnail |
| std::vector<uint8_t> thumbnail; |
| if (request_metadata.exists(ANDROID_JPEG_THUMBNAIL_SIZE)) { |
| entry = request_metadata.find(ANDROID_JPEG_THUMBNAIL_SIZE); |
| if (entry.count < 2) { |
| LOGF(ERROR) << "Thumbnail size in metadata is not complete."; |
| return -EINVAL; |
| } |
| int thumbnail_width = entry.data.i32[0]; |
| int thumbnail_height = entry.data.i32[1]; |
| if (thumbnail_width == 0 && thumbnail_height == 0) { |
| VLOGF(1) << "Thumbnail size = (0, 0), nothing will be generated"; |
| } else if (!ValidateThumbnailSize(static_metadata, thumbnail_width, |
| thumbnail_height)) { |
| LOGF(ERROR) << "Invalid thumbnail size " << thumbnail_width << "x" |
| << thumbnail_height; |
| return -EINVAL; |
| } else { |
| const uint8_t* i420_data; |
| if (in_frame.GetFourcc() == V4L2_PIX_FMT_YUV420) { |
| i420_data = in_frame.GetData(); |
| } else { |
| if (!SharedFrameBuffer::Reallocate( |
| in_frame.GetWidth(), in_frame.GetHeight(), V4L2_PIX_FMT_YUV420, |
| &temp_i420_frame_)) { |
| LOGF(ERROR) << "Failed to allocate shared memory buffer"; |
| return -EINVAL; |
| } |
| int ret = |
| image_processor_->ConvertFormat(in_frame, temp_i420_frame_.get()); |
| if (ret) { |
| LOGF(ERROR) << "Failed to convert frame to I420"; |
| return ret; |
| } |
| i420_data = temp_i420_frame_->GetData(); |
| } |
| uint32_t thumbnail_data_size = 0; |
| thumbnail.resize(kApp1MaxDataSize); |
| if (jpeg_compressor_->GenerateThumbnail( |
| i420_data, in_frame.GetWidth(), in_frame.GetHeight(), |
| thumbnail_width, thumbnail_height, thumbnail_jpeg_quality, |
| thumbnail.size(), thumbnail.data(), &thumbnail_data_size)) { |
| thumbnail.resize(thumbnail_data_size); |
| } else { |
| LOGF(WARNING) << "Generate JPEG thumbnail failed"; |
| thumbnail.clear(); |
| } |
| } |
| } |
| |
| // TODO(shik): Regenerate if thumbnail is too large. |
| if (!utils.GenerateApp1(thumbnail.data(), thumbnail.size())) { |
| LOGF(ERROR) << "Generating APP1 segment failed."; |
| return -EINVAL; |
| } |
| |
| uint32_t jpeg_data_size = 0; |
| if (!jpeg_compressor_->CompressImageFromHandle( |
| in_frame.GetBufferHandle(), out_frame->GetBufferHandle(), |
| in_frame.GetWidth(), in_frame.GetHeight(), jpeg_quality, |
| utils.GetApp1Buffer(), utils.GetApp1Length(), &jpeg_data_size)) { |
| LOGF(ERROR) << "JPEG image compression failed"; |
| return -EINVAL; |
| } |
| InsertJpegBlob(out_frame, jpeg_data_size); |
| return 0; |
| } |
| |
| void CachedFrame::DetectFaces(const FrameBuffer& input_nv12_frame, |
| std::vector<human_sensing::CrosFace>* faces) { |
| // Number of frames to detect faces once. |
| const int kFaceDetectFrameInterval = 10; |
| DCHECK(faces); |
| |
| if (!face_detector_) { |
| faces->clear(); |
| return; |
| } |
| |
| frame_count_ = (frame_count_ + 1) % kFaceDetectFrameInterval; |
| if (frame_count_ != 1) { |
| *faces = faces_; |
| return; |
| } |
| |
| FaceDetectResult ret = face_detector_->Detect( |
| input_nv12_frame.GetBufferHandle(), &faces_, active_array_size_); |
| if (ret != FaceDetectResult::kDetectOk) { |
| faces_.clear(); |
| } |
| *faces = faces_; |
| } |
| |
| static void InsertJpegBlob(FrameBuffer* out_frame, uint32_t jpeg_data_size) { |
| camera3_jpeg_blob_t blob; |
| blob.jpeg_blob_id = CAMERA3_JPEG_BLOB_ID; |
| blob.jpeg_size = jpeg_data_size; |
| memcpy(out_frame->GetData() + out_frame->GetBufferSize() - sizeof(blob), |
| &blob, sizeof(blob)); |
| } |
| |
| static bool SetExifTags(const android::CameraMetadata& static_metadata, |
| const android::CameraMetadata& request_metadata, |
| const FrameBuffer& in_frame, |
| ExifUtils* utils) { |
| if (!utils->SetImageWidth(in_frame.GetWidth()) || |
| !utils->SetImageLength(in_frame.GetHeight())) { |
| LOGF(ERROR) << "Setting image resolution failed."; |
| return false; |
| } |
| |
| struct timespec tp; |
| struct tm time_info; |
| bool time_available = clock_gettime(CLOCK_REALTIME, &tp) != -1; |
| tzset(); |
| localtime_r(&tp.tv_sec, &time_info); |
| if (!utils->SetDateTime(time_info)) { |
| LOGF(ERROR) << "Setting data time failed."; |
| return false; |
| } |
| |
| if (request_metadata.exists(ANDROID_LENS_FOCAL_LENGTH)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_LENS_FOCAL_LENGTH); |
| float focal_length = entry.data.f[0]; |
| if (!utils->SetFocalLength( |
| static_cast<uint32_t>(focal_length * kRationalPrecision), |
| kRationalPrecision)) { |
| LOGF(ERROR) << "Setting focal length failed."; |
| return false; |
| } |
| } else { |
| if (static_metadata.exists(ANDROID_LENS_FACING)) { |
| camera_metadata_ro_entry entry = |
| static_metadata.find(ANDROID_LENS_FACING); |
| if (entry.data.u8[0] != ANDROID_LENS_FACING_EXTERNAL) { |
| LOGF(ERROR) |
| << "Cannot find focal length in metadata from a built-in camera."; |
| return false; |
| } |
| } else { |
| LOGF(WARNING) << "Cannot find focal length in metadata."; |
| } |
| } |
| |
| if (request_metadata.exists(ANDROID_JPEG_GPS_COORDINATES)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_JPEG_GPS_COORDINATES); |
| if (entry.count < 3) { |
| LOGF(ERROR) << "Gps coordinates in metadata is not complete."; |
| return false; |
| } |
| if (!utils->SetGpsLatitude(entry.data.d[0])) { |
| LOGF(ERROR) << "Setting gps latitude failed."; |
| return false; |
| } |
| if (!utils->SetGpsLongitude(entry.data.d[1])) { |
| LOGF(ERROR) << "Setting gps longitude failed."; |
| return false; |
| } |
| if (!utils->SetGpsAltitude(entry.data.d[2])) { |
| LOGF(ERROR) << "Setting gps altitude failed."; |
| return false; |
| } |
| } |
| |
| if (request_metadata.exists(ANDROID_JPEG_GPS_PROCESSING_METHOD)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_JPEG_GPS_PROCESSING_METHOD); |
| std::string method_str(reinterpret_cast<const char*>(entry.data.u8)); |
| if (!utils->SetGpsProcessingMethod(method_str)) { |
| LOGF(ERROR) << "Setting gps processing method failed."; |
| return false; |
| } |
| } |
| |
| if (time_available && request_metadata.exists(ANDROID_JPEG_GPS_TIMESTAMP)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_JPEG_GPS_TIMESTAMP); |
| time_t timestamp = static_cast<time_t>(entry.data.i64[0]); |
| if (gmtime_r(×tamp, &time_info)) { |
| if (!utils->SetGpsTimestamp(time_info)) { |
| LOGF(ERROR) << "Setting gps timestamp failed."; |
| return false; |
| } |
| } else { |
| LOGF(ERROR) << "Time tranformation failed."; |
| return false; |
| } |
| } |
| |
| if (request_metadata.exists(ANDROID_JPEG_ORIENTATION)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_JPEG_ORIENTATION); |
| if (!utils->SetOrientation(entry.data.i32[0])) { |
| LOGF(ERROR) << "Setting orientation failed."; |
| return false; |
| } |
| } |
| |
| if (request_metadata.exists(ANDROID_LENS_APERTURE)) { |
| const int kAperturePrecision = 10000; |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_LENS_APERTURE); |
| if (!utils->SetFNumber(entry.data.f[0] * kAperturePrecision, |
| kAperturePrecision)) { |
| LOGF(ERROR) << "Setting F number failed."; |
| return false; |
| } |
| } |
| |
| if (static_metadata.exists(ANDROID_FLASH_INFO_AVAILABLE)) { |
| camera_metadata_ro_entry entry = |
| static_metadata.find(ANDROID_FLASH_INFO_AVAILABLE); |
| if (entry.data.u8[0] == ANDROID_FLASH_INFO_AVAILABLE_FALSE) { |
| const uint32_t kNoFlashFunction = 0x20; |
| if (!utils->SetFlash(kNoFlashFunction)) { |
| LOGF(ERROR) << "Setting flash failed."; |
| return false; |
| } |
| } else { |
| LOGF(ERROR) << "Unsupported flash info: " << entry.data.u8[0]; |
| return false; |
| } |
| } |
| |
| if (request_metadata.exists(ANDROID_CONTROL_AWB_MODE)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_CONTROL_AWB_MODE); |
| if (entry.data.u8[0] == ANDROID_CONTROL_AWB_MODE_AUTO) { |
| const uint16_t kAutoWhiteBalance = 0; |
| if (!utils->SetWhiteBalance(kAutoWhiteBalance)) { |
| LOGF(ERROR) << "Setting white balance failed."; |
| return false; |
| } |
| } else { |
| LOGF(ERROR) << "Unsupported awb mode: " << entry.data.u8[0]; |
| return false; |
| } |
| } |
| |
| if (request_metadata.exists(ANDROID_CONTROL_AE_MODE)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_CONTROL_AE_MODE); |
| const uint16_t kExposureMode = |
| (entry.data.u8[0] == ANDROID_CONTROL_AE_MODE_OFF) ? 1 : 0; |
| if (!utils->SetExposureMode(kExposureMode)) { |
| LOGF(ERROR) << "Setting exposure mode failed."; |
| return false; |
| } |
| } |
| |
| if (request_metadata.exists(ANDROID_SENSOR_EXPOSURE_TIME)) { |
| camera_metadata_ro_entry entry = |
| request_metadata.find(ANDROID_SENSOR_EXPOSURE_TIME); |
| if (!utils->SetExposureTime(entry.data.i64[0], 1'000'000'000)) { |
| LOGF(ERROR) << "Setting exposure time failed."; |
| return false; |
| } |
| } |
| |
| if (time_available) { |
| char str[4]; |
| if (snprintf(str, sizeof(str), "%03ld", tp.tv_nsec / 1000000) < 0) { |
| LOGF(ERROR) << "Subsec is invalid: " << tp.tv_nsec; |
| return false; |
| } |
| if (!utils->SetSubsecTime(std::string(str))) { |
| LOGF(ERROR) << "Setting subsec time failed."; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace cros |