v4l2 camera: port ARC++ image processing code

Utiltizes ARC++ image processing code to perform the conversion between
the camera format and the requested format from the framework. In order
to support USB devices which may not implement the required formats,
conversion between YUYV/MJPEG to the request format is done on-the-fly.

TEST: All existing unit tests pass. Tested manually with the camera
sample APK, preview/video/capture in both JPEG and YUV formats. Tested
on the RPi v2 Camera, the Logitech C270 and the 180 degree Fisheye USB
camera.
Bug: 37708856, Bug: 69983918
Exempt-From-Owner-Approval: This HAL is owned by AT team.
Change-Id: I1a54581d1170fa3270036ec366fb613fe587a094
diff --git a/modules/camera/3_4/Android.mk b/modules/camera/3_4/Android.mk
index 05ddf5a..6ecedac 100644
--- a/modules/camera/3_4/Android.mk
+++ b/modules/camera/3_4/Android.mk
@@ -22,21 +22,31 @@
 
 v4l2_shared_libs := \
   libbase \
+  libchrome \
   libcamera_client \
   libcamera_metadata \
   libcutils \
+  libexif \
   libhardware \
   liblog \
   libsync \
   libutils \
 
-v4l2_static_libs :=
+v4l2_static_libs := \
+  libyuv_static \
+  libjpeg_static_ndk \
 
-v4l2_cflags := -fno-short-enums -Wall -Wno-error -Wextra -fvisibility=hidden
+v4l2_cflags := -fno-short-enums -Wall -Wno-error -Wextra -fvisibility=hidden -DHAVE_JPEG
 
-v4l2_c_includes := $(call include-path-for, camera)
+v4l2_c_includes := $(call include-path-for, camera) \
+  external/libyuv/files/include \
 
 v4l2_src_files := \
+  arc/cached_frame.cpp \
+  arc/exif_utils.cpp \
+  arc/frame_buffer.cpp \
+  arc/image_processor.cpp \
+  arc/jpeg_compressor.cpp \
   camera.cpp \
   capture_request.cpp \
   format_metadata_factory.cpp \
@@ -49,7 +59,6 @@
   stream_format.cpp \
   v4l2_camera.cpp \
   v4l2_camera_hal.cpp \
-  v4l2_gralloc.cpp \
   v4l2_metadata_factory.cpp \
   v4l2_wrapper.cpp \
 
diff --git a/modules/camera/3_4/README.md b/modules/camera/3_4/README.md
index 30f36d7..168ba97 100644
--- a/modules/camera/3_4/README.md
+++ b/modules/camera/3_4/README.md
@@ -28,7 +28,7 @@
 Devices and cameras wishing to use this HAL must meet
 the following requirements:
 
-* The camera must support RGB24, YUV420, and JPEG formats.
+* The camera must support BGR32, YUV420, and JPEG formats.
 * The gralloc and other graphics modules used by the device must use
 `HAL_PIXEL_FORMAT_RGBA_8888` as the `HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED`
 
diff --git a/modules/camera/3_4/arc/cached_frame.cpp b/modules/camera/3_4/arc/cached_frame.cpp
new file mode 100644
index 0000000..0489be8
--- /dev/null
+++ b/modules/camera/3_4/arc/cached_frame.cpp
@@ -0,0 +1,218 @@
+/* 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 <errno.h>
+#include <libyuv.h>
+
+#include "arc/common.h"
+#include "arc/common_types.h"
+
+namespace arc {
+
+using android::CameraMetadata;
+
+CachedFrame::CachedFrame()
+    : source_frame_(nullptr),
+      cropped_buffer_capacity_(0),
+      yu12_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
diff --git a/modules/camera/3_4/arc/cached_frame.h b/modules/camera/3_4/arc/cached_frame.h
new file mode 100644
index 0000000..fbfcb76
--- /dev/null
+++ b/modules/camera/3_4/arc/cached_frame.h
@@ -0,0 +1,86 @@
+/* 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.
+ */
+
+#ifndef HAL_USB_CACHED_FRAME_H_
+#define HAL_USB_CACHED_FRAME_H_
+
+#include <memory>
+
+#include <camera/CameraMetadata.h>
+
+#include "arc/image_processor.h"
+
+namespace arc {
+
+// CachedFrame contains a source FrameBuffer and a cached, converted
+// FrameBuffer. The incoming frames would be converted to YU12, the default
+// format of libyuv, to allow convenient processing.
+class CachedFrame {
+ public:
+  CachedFrame();
+  ~CachedFrame();
+
+  // SetSource() doesn't take ownership of |frame|. The caller can only release
+  // |frame| after calling UnsetSource(). SetSource() immediately converts
+  // incoming frame into YU12. Return non-zero values if it encounters errors.
+  // If |rotate_degree| is 90 or 270, |frame| will be cropped, rotated by the
+  // specified amount and scaled.
+  // If |rotate_degree| is -1, |frame| will not be cropped, rotated, and scaled.
+  // This function will return an error if |rotate_degree| is not -1, 90, or
+  // 270.
+  int SetSource(const FrameBuffer* frame, int rotate_degree);
+  void UnsetSource();
+
+  uint8_t* GetSourceBuffer() const;
+  size_t GetSourceDataSize() const;
+  uint32_t GetSourceFourCC() const;
+  uint8_t* GetCachedBuffer() const;
+  uint32_t GetCachedFourCC() const;
+
+  uint32_t GetWidth() const;
+  uint32_t GetHeight() const;
+
+  // Calculate the output buffer size when converting to the specified pixel
+  // format. |fourcc| is defined as V4L2_PIX_FMT_* in linux/videodev2.h. Return
+  // 0 on error.
+  size_t GetConvertedSize(int fourcc) const;
+
+  // Caller should fill everything except |data_size| and |fd| of |out_frame|.
+  // The function will do format conversion and scale to fit |out_frame|
+  // requirement.
+  // If |video_hack| is true, it outputs YU12 when |hal_pixel_format| is YV12
+  // (swapping U/V planes). Caller should fill |fourcc|, |data|, and
+  // Return non-zero error code on failure; return 0 on success.
+  int Convert(const android::CameraMetadata& metadata, FrameBuffer* out_frame,
+              bool video_hack = false);
+
+ private:
+  int ConvertToYU12();
+  // When we have a landscape mounted camera and the current camera activity is
+  // portrait, the frames shown in the activity would be stretched. Therefore,
+  // we want to simulate a native portrait camera. That's why we want to crop,
+  // rotate |rotate_degree| clockwise and scale the frame. HAL would not change
+  // CameraInfo.orientation. Instead, framework would fake the
+  // CameraInfo.orientation. Framework would then tell HAL how much the frame
+  // needs to rotate clockwise by |rotate_degree|.
+  int CropRotateScale(int rotate_degree);
+
+  const FrameBuffer* source_frame_;
+  // const V4L2FrameBuffer* source_frame_;
+
+  // Temporary buffer for cropped and rotated results.
+  std::unique_ptr<uint8_t[]> cropped_buffer_;
+  size_t cropped_buffer_capacity_;
+
+  // Cache YU12 decoded results.
+  std::unique_ptr<AllocatedFrameBuffer> yu12_frame_;
+
+  // Temporary buffer for scaled results.
+  std::unique_ptr<AllocatedFrameBuffer> scaled_frame_;
+};
+
+}  // namespace arc
+
+#endif  // HAL_USB_CACHED_FRAME_H_
diff --git a/modules/camera/3_4/arc/common.h b/modules/camera/3_4/arc/common.h
new file mode 100644
index 0000000..25e4eb5
--- /dev/null
+++ b/modules/camera/3_4/arc/common.h
@@ -0,0 +1,28 @@
+/* Copyright 2016 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.
+ */
+
+#ifndef INCLUDE_ARC_COMMON_H_
+#define INCLUDE_ARC_COMMON_H_
+
+#include <string>
+
+#include <base/logging.h>
+
+#define LOGF(level) LOG(level) << __FUNCTION__ << "(): "
+#define LOGFID(level, id) LOG(level) << __FUNCTION__ << "(): id: " << id << ": "
+#define LOGF_IF(level, res) LOG_IF(level, res) << __FUNCTION__ << "(): "
+
+#define VLOGF(level) VLOG(level) << __FUNCTION__ << "(): "
+#define VLOGFID(level, id) \
+  VLOG(level) << __FUNCTION__ << "(): id: " << id << ": "
+
+#define VLOGF_ENTER() VLOGF(1) << "enter"
+#define VLOGF_EXIT() VLOGF(1) << "exit"
+
+inline std::string FormatToString(int32_t format) {
+  return std::string(reinterpret_cast<char*>(&format), 4);
+}
+
+#endif  // INCLUDE_ARC_COMMON_H_
diff --git a/modules/camera/3_4/arc/common_types.h b/modules/camera/3_4/arc/common_types.h
new file mode 100644
index 0000000..8f62ad6
--- /dev/null
+++ b/modules/camera/3_4/arc/common_types.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 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.
+ */
+
+#ifndef HAL_USB_COMMON_TYPES_H_
+#define HAL_USB_COMMON_TYPES_H_
+
+#include <string>
+#include <vector>
+
+namespace arc {
+
+struct DeviceInfo {
+  // ex: /dev/video0
+  std::string device_path;
+  // USB vender id
+  std::string usb_vid;
+  // USB product id
+  std::string usb_pid;
+  // Some cameras need to wait several frames to output correct images.
+  uint32_t frames_to_skip_after_streamon;
+
+  // Member definitions can be found in https://developer.android.com/
+  // reference/android/hardware/camera2/CameraCharacteristics.html
+  uint32_t lens_facing;
+  int32_t sensor_orientation;
+  float horizontal_view_angle_16_9;
+  float horizontal_view_angle_4_3;
+  std::vector<float> lens_info_available_focal_lengths;
+  float lens_info_minimum_focus_distance;
+  float lens_info_optimal_focus_distance;
+  float vertical_view_angle_16_9;
+  float vertical_view_angle_4_3;
+};
+
+typedef std::vector<DeviceInfo> DeviceInfos;
+
+struct SupportedFormat {
+  uint32_t width;
+  uint32_t height;
+  uint32_t fourcc;
+  // All the supported frame rates in fps with given width, height, and
+  // pixelformat. This is not sorted. For example, suppose width, height, and
+  // fourcc are 640x480 YUYV. If frameRates are 15.0 and 30.0, the camera
+  // supports outputting 640X480 YUYV in 15fps or 30fps.
+  std::vector<float> frameRates;
+};
+
+typedef std::vector<SupportedFormat> SupportedFormats;
+
+}  // namespace arc
+
+#endif  // HAL_USB_COMMON_TYPES_H_
diff --git a/modules/camera/3_4/arc/exif_utils.cpp b/modules/camera/3_4/arc/exif_utils.cpp
new file mode 100644
index 0000000..aec53c5
--- /dev/null
+++ b/modules/camera/3_4/arc/exif_utils.cpp
@@ -0,0 +1,513 @@
+/*
+ * 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/exif_utils.h"
+
+#include <cstdlib>
+#include <ctime>
+
+#include <libyuv.h>
+
+#include "arc/common.h"
+
+namespace std {
+
+template <>
+struct default_delete<ExifEntry> {
+  inline void operator()(ExifEntry* entry) const { exif_entry_unref(entry); }
+};
+
+}  // namespace std
+
+namespace arc {
+
+// This comes from the Exif Version 2.3 standard table 9.
+const uint8_t gExifAsciiPrefix[] = {0x41, 0x53, 0x43, 0x49,
+                                    0x49, 0x0,  0x0,  0x0};
+
+static void SetLatitudeOrLongitudeData(unsigned char* data, double num) {
+  // Take the integer part of |num|.
+  ExifLong degrees = static_cast<ExifLong>(num);
+  ExifLong minutes = static_cast<ExifLong>(60 * (num - degrees));
+  ExifLong microseconds =
+      static_cast<ExifLong>(3600000000u * (num - degrees - minutes / 60.0));
+  exif_set_rational(data, EXIF_BYTE_ORDER_INTEL, {degrees, 1});
+  exif_set_rational(data + sizeof(ExifRational), EXIF_BYTE_ORDER_INTEL,
+                    {minutes, 1});
+  exif_set_rational(data + 2 * sizeof(ExifRational), EXIF_BYTE_ORDER_INTEL,
+                    {microseconds, 1000000});
+}
+
+ExifUtils::ExifUtils()
+    : yu12_buffer_(nullptr),
+      yu12_width_(0),
+      yu12_height_(0),
+      thumbnail_width_(0),
+      thumbnail_height_(0),
+      exif_data_(nullptr),
+      app1_buffer_(nullptr),
+      app1_length_(0) {}
+
+ExifUtils::~ExifUtils() { Reset(); }
+
+bool ExifUtils::Initialize(const uint8_t* buffer, uint16_t width,
+                           uint16_t height, int quality) {
+  Reset();
+
+  if (width % 2 != 0 || height % 2 != 0) {
+    LOGF(ERROR) << "invalid image size " << width << "x" << height;
+    return false;
+  }
+  if (quality < 1 || quality > 100) {
+    LOGF(ERROR) << "invalid jpeg quality " << quality;
+    return false;
+  }
+  thumbnail_jpeg_quality_ = quality;
+  yu12_buffer_ = buffer;
+  yu12_width_ = width;
+  yu12_height_ = height;
+
+  exif_data_ = exif_data_new();
+  if (exif_data_ == nullptr) {
+    LOGF(ERROR) << "allocate memory for exif_data_ failed";
+    return false;
+  }
+  // Set the image options.
+  exif_data_set_option(exif_data_, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION);
+  exif_data_set_data_type(exif_data_, EXIF_DATA_TYPE_COMPRESSED);
+  exif_data_set_byte_order(exif_data_, EXIF_BYTE_ORDER_INTEL);
+
+  // Set image width and length.
+  SetImageWidth(width);
+  SetImageLength(height);
+
+  return true;
+}
+
+bool ExifUtils::SetMaker(const std::string& maker) {
+  size_t entrySize = maker.length() + 1;
+  std::unique_ptr<ExifEntry> entry = AddVariableLengthEntry(
+      EXIF_IFD_0, EXIF_TAG_MAKE, EXIF_FORMAT_ASCII, entrySize, entrySize);
+  if (!entry) {
+    LOGF(ERROR) << "Adding Make exif entry failed";
+    return false;
+  }
+  memcpy(entry->data, maker.c_str(), entrySize);
+  return true;
+}
+
+bool ExifUtils::SetModel(const std::string& model) {
+  size_t entrySize = model.length() + 1;
+  std::unique_ptr<ExifEntry> entry = AddVariableLengthEntry(
+      EXIF_IFD_0, EXIF_TAG_MODEL, EXIF_FORMAT_ASCII, entrySize, entrySize);
+  if (!entry) {
+    LOGF(ERROR) << "Adding Model exif entry failed";
+    return false;
+  }
+  memcpy(entry->data, model.c_str(), entrySize);
+  return true;
+}
+
+bool ExifUtils::SetDateTime(const struct tm& t) {
+  // The length is 20 bytes including NULL for termination in Exif standard.
+  char str[20];
+  int result = snprintf(str, sizeof(str), "%04i:%02i:%02i %02i:%02i:%02i",
+                        t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
+                        t.tm_min, t.tm_sec);
+  if (result != sizeof(str) - 1) {
+    LOGF(WARNING) << "Input time is invalid";
+    return false;
+  }
+  std::unique_ptr<ExifEntry> entry =
+      AddVariableLengthEntry(EXIF_IFD_0, EXIF_TAG_DATE_TIME, EXIF_FORMAT_ASCII,
+                             sizeof(str), sizeof(str));
+  if (!entry) {
+    LOGF(ERROR) << "Adding DateTime exif entry failed";
+    return false;
+  }
+  memcpy(entry->data, str, sizeof(str));
+  return true;
+}
+
+bool ExifUtils::SetFocalLength(uint32_t numerator, uint32_t denominator) {
+  std::unique_ptr<ExifEntry> entry =
+      AddEntry(EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH);
+  if (!entry) {
+    LOGF(ERROR) << "Adding FocalLength exif entry failed";
+    return false;
+  }
+  exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL,
+                    {numerator, denominator});
+  return true;
+}
+
+bool ExifUtils::SetGpsLatitude(double latitude) {
+  const ExifTag refTag = static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE_REF);
+  std::unique_ptr<ExifEntry> refEntry =
+      AddVariableLengthEntry(EXIF_IFD_GPS, refTag, EXIF_FORMAT_ASCII, 2, 2);
+  if (!refEntry) {
+    LOGF(ERROR) << "Adding GPSLatitudeRef exif entry failed";
+    return false;
+  }
+  if (latitude >= 0) {
+    memcpy(refEntry->data, "N", sizeof("N"));
+  } else {
+    memcpy(refEntry->data, "S", sizeof("S"));
+    latitude *= -1;
+  }
+
+  const ExifTag tag = static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE);
+  std::unique_ptr<ExifEntry> entry = AddVariableLengthEntry(
+      EXIF_IFD_GPS, tag, EXIF_FORMAT_RATIONAL, 3, 3 * sizeof(ExifRational));
+  if (!entry) {
+    exif_content_remove_entry(exif_data_->ifd[EXIF_IFD_GPS], refEntry.get());
+    LOGF(ERROR) << "Adding GPSLatitude exif entry failed";
+    return false;
+  }
+  SetLatitudeOrLongitudeData(entry->data, latitude);
+
+  return true;
+}
+
+bool ExifUtils::SetGpsLongitude(double longitude) {
+  ExifTag refTag = static_cast<ExifTag>(EXIF_TAG_GPS_LONGITUDE_REF);
+  std::unique_ptr<ExifEntry> refEntry =
+      AddVariableLengthEntry(EXIF_IFD_GPS, refTag, EXIF_FORMAT_ASCII, 2, 2);
+  if (!refEntry) {
+    LOGF(ERROR) << "Adding GPSLongitudeRef exif entry failed";
+    return false;
+  }
+  if (longitude >= 0) {
+    memcpy(refEntry->data, "E", sizeof("E"));
+  } else {
+    memcpy(refEntry->data, "W", sizeof("W"));
+    longitude *= -1;
+  }
+
+  ExifTag tag = static_cast<ExifTag>(EXIF_TAG_GPS_LONGITUDE);
+  std::unique_ptr<ExifEntry> entry = AddVariableLengthEntry(
+      EXIF_IFD_GPS, tag, EXIF_FORMAT_RATIONAL, 3, 3 * sizeof(ExifRational));
+  if (!entry) {
+    exif_content_remove_entry(exif_data_->ifd[EXIF_IFD_GPS], refEntry.get());
+    LOGF(ERROR) << "Adding GPSLongitude exif entry failed";
+    return false;
+  }
+  SetLatitudeOrLongitudeData(entry->data, longitude);
+
+  return true;
+}
+
+bool ExifUtils::SetGpsAltitude(double altitude) {
+  ExifTag refTag = static_cast<ExifTag>(EXIF_TAG_GPS_ALTITUDE_REF);
+  std::unique_ptr<ExifEntry> refEntry =
+      AddVariableLengthEntry(EXIF_IFD_GPS, refTag, EXIF_FORMAT_BYTE, 1, 1);
+  if (!refEntry) {
+    LOGF(ERROR) << "Adding GPSAltitudeRef exif entry failed";
+    return false;
+  }
+  if (altitude >= 0) {
+    *refEntry->data = 0;
+  } else {
+    *refEntry->data = 1;
+    altitude *= -1;
+  }
+
+  ExifTag tag = static_cast<ExifTag>(EXIF_TAG_GPS_ALTITUDE);
+  std::unique_ptr<ExifEntry> entry = AddVariableLengthEntry(
+      EXIF_IFD_GPS, tag, EXIF_FORMAT_RATIONAL, 1, sizeof(ExifRational));
+  if (!entry) {
+    exif_content_remove_entry(exif_data_->ifd[EXIF_IFD_GPS], refEntry.get());
+    LOGF(ERROR) << "Adding GPSAltitude exif entry failed";
+    return false;
+  }
+  exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL,
+                    {static_cast<ExifLong>(altitude * 1000), 1000});
+
+  return true;
+}
+
+bool ExifUtils::SetGpsTimestamp(const struct tm& t) {
+  const ExifTag dateTag = static_cast<ExifTag>(EXIF_TAG_GPS_DATE_STAMP);
+  const size_t kGpsDateStampSize = 11;
+  std::unique_ptr<ExifEntry> entry =
+      AddVariableLengthEntry(EXIF_IFD_GPS, dateTag, EXIF_FORMAT_ASCII,
+                             kGpsDateStampSize, kGpsDateStampSize);
+  if (!entry) {
+    LOGF(ERROR) << "Adding GPSDateStamp exif entry failed";
+    return false;
+  }
+  int result =
+      snprintf(reinterpret_cast<char*>(entry->data), kGpsDateStampSize,
+               "%04i:%02i:%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
+  if (result != kGpsDateStampSize - 1) {
+    LOGF(WARNING) << "Input time is invalid";
+    return false;
+  }
+
+  const ExifTag timeTag = static_cast<ExifTag>(EXIF_TAG_GPS_TIME_STAMP);
+  entry = AddVariableLengthEntry(EXIF_IFD_GPS, timeTag, EXIF_FORMAT_RATIONAL, 3,
+                                 3 * sizeof(ExifRational));
+  if (!entry) {
+    LOGF(ERROR) << "Adding GPSTimeStamp exif entry failed";
+    return false;
+  }
+  exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL,
+                    {static_cast<ExifLong>(t.tm_hour), 1});
+  exif_set_rational(entry->data + sizeof(ExifRational), EXIF_BYTE_ORDER_INTEL,
+                    {static_cast<ExifLong>(t.tm_min), 1});
+  exif_set_rational(entry->data + 2 * sizeof(ExifRational),
+                    EXIF_BYTE_ORDER_INTEL,
+                    {static_cast<ExifLong>(t.tm_sec), 1});
+
+  return true;
+}
+
+bool ExifUtils::SetGpsProcessingMethod(const std::string& method) {
+  ExifTag tag = static_cast<ExifTag>(EXIF_TAG_GPS_PROCESSING_METHOD);
+  size_t size = sizeof(gExifAsciiPrefix) + method.length();
+  std::unique_ptr<ExifEntry> entry = AddVariableLengthEntry(
+      EXIF_IFD_GPS, tag, EXIF_FORMAT_UNDEFINED, size, size);
+  if (!entry) {
+    LOGF(ERROR) << "Adding GPSProcessingMethod exif entry failed";
+    return false;
+  }
+  memcpy(entry->data, gExifAsciiPrefix, sizeof(gExifAsciiPrefix));
+  // Since the exif format is undefined, NULL termination is not necessary.
+  memcpy(entry->data + sizeof(gExifAsciiPrefix), method.c_str(),
+         method.length());
+
+  return true;
+}
+
+bool ExifUtils::SetThumbnailSize(uint16_t width, uint16_t height) {
+  if (width % 2 != 0 || height % 2 != 0) {
+    LOGF(ERROR) << "Invalid thumbnail size " << width << "x" << height;
+    return false;
+  }
+  thumbnail_width_ = width;
+  thumbnail_height_ = height;
+  return true;
+}
+
+bool ExifUtils::SetOrientation(uint16_t orientation) {
+  std::unique_ptr<ExifEntry> entry = AddEntry(EXIF_IFD_0, EXIF_TAG_ORIENTATION);
+  if (!entry) {
+    LOGF(ERROR) << "Adding Orientation exif entry failed";
+    return false;
+  }
+  /*
+   * Orientation value:
+   *  1      2      3      4      5          6          7          8
+   *
+   *  888888 888888     88 88     8888888888 88                 88 8888888888
+   *  88         88     88 88     88  88     88  88         88  88     88  88
+   *  8888     8888   8888 8888   88         8888888888 8888888888         88
+   *  88         88     88 88
+   *  88         88 888888 888888
+   */
+  int value = 1;
+  switch (orientation) {
+    case 90:
+      value = 6;
+      break;
+    case 180:
+      value = 3;
+      break;
+    case 270:
+      value = 8;
+      break;
+    default:
+      break;
+  }
+  exif_set_short(entry->data, EXIF_BYTE_ORDER_INTEL, value);
+  return true;
+}
+
+bool ExifUtils::GenerateApp1() {
+  DestroyApp1();
+  if (thumbnail_width_ > 0 && thumbnail_height_ > 0) {
+    if (!GenerateThumbnail()) {
+      LOGF(ERROR) << "Generate thumbnail image failed";
+      return false;
+    }
+    exif_data_->data = const_cast<uint8_t*>(
+        static_cast<const uint8_t*>(compressor_.GetCompressedImagePtr()));
+    exif_data_->size = compressor_.GetCompressedImageSize();
+  }
+  // Save the result into |app1_buffer_|.
+  exif_data_save_data(exif_data_, &app1_buffer_, &app1_length_);
+  if (!app1_length_) {
+    LOGF(ERROR) << "Allocate memory for app1_buffer_ failed";
+    return false;
+  }
+  /*
+   * The JPEG segment size is 16 bits in spec. The size of APP1 segment should
+   * be smaller than 65533 because there are two bytes for segment size field.
+   */
+  if (app1_length_ > 65533) {
+    DestroyApp1();
+    LOGF(ERROR) << "The size of APP1 segment is too large";
+    return false;
+  }
+  return true;
+}
+
+const uint8_t* ExifUtils::GetApp1Buffer() { return app1_buffer_; }
+
+unsigned int ExifUtils::GetApp1Length() { return app1_length_; }
+
+void ExifUtils::Reset() {
+  yu12_buffer_ = nullptr;
+  yu12_width_ = 0;
+  yu12_height_ = 0;
+  thumbnail_width_ = 0;
+  thumbnail_height_ = 0;
+  DestroyApp1();
+  if (exif_data_) {
+    /*
+     * Since we decided to ignore the original APP1, we are sure that there is
+     * no thumbnail allocated by libexif. |exif_data_->data| is actually
+     * allocated by JpegCompressor. Sets |exif_data_->data| to nullptr to
+     * prevent exif_data_unref() destroy it incorrectly.
+     */
+    exif_data_->data = nullptr;
+    exif_data_->size = 0;
+    exif_data_unref(exif_data_);
+    exif_data_ = nullptr;
+  }
+}
+
+std::unique_ptr<ExifEntry> ExifUtils::AddVariableLengthEntry(
+    ExifIfd ifd, ExifTag tag, ExifFormat format, uint64_t components,
+    unsigned int size) {
+  // Remove old entry if exists.
+  exif_content_remove_entry(exif_data_->ifd[ifd],
+                            exif_content_get_entry(exif_data_->ifd[ifd], tag));
+  ExifMem* mem = exif_mem_new_default();
+  if (!mem) {
+    LOGF(ERROR) << "Allocate memory for exif entry failed";
+    return nullptr;
+  }
+  std::unique_ptr<ExifEntry> entry(exif_entry_new_mem(mem));
+  if (!entry) {
+    LOGF(ERROR) << "Allocate memory for exif entry failed";
+    exif_mem_unref(mem);
+    return nullptr;
+  }
+  void* tmpBuffer = exif_mem_alloc(mem, size);
+  if (!tmpBuffer) {
+    LOGF(ERROR) << "Allocate memory for exif entry failed";
+    exif_mem_unref(mem);
+    return nullptr;
+  }
+
+  entry->data = static_cast<unsigned char*>(tmpBuffer);
+  entry->tag = tag;
+  entry->format = format;
+  entry->components = components;
+  entry->size = size;
+
+  exif_content_add_entry(exif_data_->ifd[ifd], entry.get());
+  exif_mem_unref(mem);
+
+  return entry;
+}
+
+std::unique_ptr<ExifEntry> ExifUtils::AddEntry(ExifIfd ifd, ExifTag tag) {
+  std::unique_ptr<ExifEntry> entry(
+      exif_content_get_entry(exif_data_->ifd[ifd], tag));
+  if (entry) {
+    // exif_content_get_entry() won't ref the entry, so we ref here.
+    exif_entry_ref(entry.get());
+    return entry;
+  }
+  entry.reset(exif_entry_new());
+  if (!entry) {
+    LOGF(ERROR) << "Allocate memory for exif entry failed";
+    return nullptr;
+  }
+  entry->tag = tag;
+  exif_content_add_entry(exif_data_->ifd[ifd], entry.get());
+  exif_entry_initialize(entry.get(), tag);
+  return entry;
+}
+
+bool ExifUtils::SetImageWidth(uint16_t width) {
+  std::unique_ptr<ExifEntry> entry = AddEntry(EXIF_IFD_0, EXIF_TAG_IMAGE_WIDTH);
+  if (!entry) {
+    LOGF(ERROR) << "Adding ImageWidth exif entry failed";
+    return false;
+  }
+  exif_set_short(entry->data, EXIF_BYTE_ORDER_INTEL, width);
+  return true;
+}
+
+bool ExifUtils::SetImageLength(uint16_t length) {
+  std::unique_ptr<ExifEntry> entry =
+      AddEntry(EXIF_IFD_0, EXIF_TAG_IMAGE_LENGTH);
+  if (!entry) {
+    LOGF(ERROR) << "Adding ImageLength exif entry failed";
+    return false;
+  }
+  exif_set_short(entry->data, EXIF_BYTE_ORDER_INTEL, length);
+  return true;
+}
+
+bool ExifUtils::GenerateThumbnail() {
+  // Resize yuv image to |thumbnail_width_| x |thumbnail_height_|.
+  std::vector<uint8_t> scaled_buffer;
+  if (!GenerateYuvThumbnail(&scaled_buffer)) {
+    LOGF(ERROR) << "Generate YUV thumbnail failed";
+    return false;
+  }
+
+  // Compress thumbnail to JPEG.
+  if (!compressor_.CompressImage(scaled_buffer.data(), thumbnail_width_,
+                                 thumbnail_height_, thumbnail_jpeg_quality_,
+                                 NULL, 0)) {
+    LOGF(ERROR) << "Compress thumbnail failed";
+    return false;
+  }
+  return true;
+}
+
+bool ExifUtils::GenerateYuvThumbnail(std::vector<uint8_t>* scaled_buffer) {
+  size_t y_plane_size = yu12_width_ * yu12_height_;
+  const uint8* y_plane = yu12_buffer_;
+  const uint8* u_plane = y_plane + y_plane_size;
+  const uint8* v_plane = u_plane + y_plane_size / 4;
+
+  size_t scaled_y_plane_size = thumbnail_width_ * thumbnail_height_;
+  scaled_buffer->resize(scaled_y_plane_size * 3 / 2);
+  uint8* scaled_y_plane = scaled_buffer->data();
+  uint8* scaled_u_plane = scaled_y_plane + scaled_y_plane_size;
+  uint8* scaled_v_plane = scaled_u_plane + scaled_y_plane_size / 4;
+
+  int result = libyuv::I420Scale(
+      y_plane, yu12_width_, u_plane, yu12_width_ / 2, v_plane, yu12_width_ / 2,
+      yu12_width_, yu12_height_, scaled_y_plane, thumbnail_width_,
+      scaled_u_plane, thumbnail_width_ / 2, scaled_v_plane,
+      thumbnail_width_ / 2, thumbnail_width_, thumbnail_height_,
+      libyuv::kFilterNone);
+  if (result != 0) {
+    LOGF(ERROR) << "Scale I420 image failed";
+    return false;
+  }
+  return true;
+}
+
+void ExifUtils::DestroyApp1() {
+  /*
+   * Since there is no API to access ExifMem in ExifData->priv, we use free
+   * here, which is the default free function in libexif. See
+   * exif_data_save_data() for detail.
+   */
+  free(app1_buffer_);
+  app1_buffer_ = nullptr;
+  app1_length_ = 0;
+}
+
+}  // namespace arc
diff --git a/modules/camera/3_4/arc/exif_utils.h b/modules/camera/3_4/arc/exif_utils.h
new file mode 100644
index 0000000..956ee1d
--- /dev/null
+++ b/modules/camera/3_4/arc/exif_utils.h
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#ifndef INCLUDE_ARC_EXIF_UTILS_H_
+#define INCLUDE_ARC_EXIF_UTILS_H_
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+extern "C" {
+#include <libexif/exif-data.h>
+}
+
+#include "arc/jpeg_compressor.h"
+
+namespace arc {
+
+// ExifUtils can generate APP1 segment with tags which caller set. ExifUtils can
+// also add a thumbnail in the APP1 segment if thumbnail size is specified.
+// ExifUtils can be reused with different images by calling initialize().
+//
+// Example of using this class :
+//  ExifUtils utils;
+//  utils.initialize(inputYU12Buffer, inputWidth, inputHeight,
+//                   outputJpegQuality);
+//  ...
+//  // Call ExifUtils functions to set Exif tags.
+//  ...
+//  utils.generateApp1();
+//  unsigned int app1Length = utils.getApp1Length();
+//  uint8_t* app1Buffer = new uint8_t[app1Length];
+//  memcpy(app1Buffer, utils.getApp1Buffer(), app1Length);
+class ExifUtils {
+ public:
+  ExifUtils();
+  ~ExifUtils();
+
+  // Sets input YU12 image |buffer| with |width| x |height|. |quality| is the
+  // compressed JPEG image quality. The caller should not release |buffer| until
+  // generateApp1() or the destructor is called. initialize() can be called
+  // multiple times. The setting of Exif tags will be cleared.
+  bool Initialize(const uint8_t* buffer, uint16_t width, uint16_t height,
+                  int quality);
+
+  // Sets the manufacturer of camera.
+  // Returns false if memory allocation fails.
+  bool SetMaker(const std::string& maker);
+
+  // Sets the model number of camera.
+  // Returns false if memory allocation fails.
+  bool SetModel(const std::string& model);
+
+  // Sets the date and time of image last modified. It takes local time. The
+  // name of the tag is DateTime in IFD0.
+  // Returns false if memory allocation fails.
+  bool SetDateTime(const struct tm& t);
+
+  // Sets the focal length of lens used to take the image in millimeters.
+  // Returns false if memory allocation fails.
+  bool SetFocalLength(uint32_t numerator, uint32_t denominator);
+
+  // Sets the latitude with degrees minutes seconds format.
+  // Returns false if memory allocation fails.
+  bool SetGpsLatitude(double latitude);
+
+  // Sets the longitude with degrees minutes seconds format.
+  // Returns false if memory allocation fails.
+  bool SetGpsLongitude(double longitude);
+
+  // Sets the altitude in meters.
+  // Returns false if memory allocation fails.
+  bool SetGpsAltitude(double altitude);
+
+  // Sets GPS date stamp and time stamp (atomic clock). It takes UTC time.
+  // Returns false if memory allocation fails.
+  bool SetGpsTimestamp(const struct tm& t);
+
+  // Sets GPS processing method.
+  // Returns false if memory allocation fails.
+  bool SetGpsProcessingMethod(const std::string& method);
+
+  // Since the size of APP1 segment is limited, it is recommended the
+  // resolution of thumbnail is equal to or smaller than 640x480. If the
+  // thumbnail is too big, generateApp1() will return false.
+  // Returns false if |width| or |height| is not even.
+  bool SetThumbnailSize(uint16_t width, uint16_t height);
+
+  // Sets image orientation.
+  // Returns false if memory allocation fails.
+  bool SetOrientation(uint16_t orientation);
+
+  // Generates APP1 segment.
+  // Returns false if generating APP1 segment fails.
+  bool GenerateApp1();
+
+  // Gets buffer of APP1 segment. This method must be called only after calling
+  // generateAPP1().
+  const uint8_t* GetApp1Buffer();
+
+  // Gets length of APP1 segment. This method must be called only after calling
+  // generateAPP1().
+  unsigned int GetApp1Length();
+
+ private:
+  // Resets the pointers and memories.
+  void Reset();
+
+  // Adds a variable length tag to |exif_data_|. It will remove the original one
+  // if the tag exists.
+  // Returns the entry of the tag. The reference count of returned ExifEntry is
+  // two.
+  std::unique_ptr<ExifEntry> AddVariableLengthEntry(ExifIfd ifd, ExifTag tag,
+                                                    ExifFormat format,
+                                                    uint64_t components,
+                                                    unsigned int size);
+
+  // Adds a entry of |tag| in |exif_data_|. It won't remove the original one if
+  // the tag exists.
+  // Returns the entry of the tag. It adds one reference count to returned
+  // ExifEntry.
+  std::unique_ptr<ExifEntry> AddEntry(ExifIfd ifd, ExifTag tag);
+
+  // Sets the width (number of columes) of main image.
+  // Returns false if memory allocation fails.
+  bool SetImageWidth(uint16_t width);
+
+  // Sets the length (number of rows) of main image.
+  // Returns false if memory allocation fails.
+  bool SetImageLength(uint16_t length);
+
+  // Generates a thumbnail. Calls compressor_.getCompressedImagePtr() to get the
+  // result image.
+  // Returns false if failed.
+  bool GenerateThumbnail();
+
+  // Resizes the thumbnail yuv image to |thumbnail_width_| x |thumbnail_height_|
+  // and stores in |scaled_buffer|.
+  // Returns false if scale image failed.
+  bool GenerateYuvThumbnail(std::vector<uint8_t>* scaled_buffer);
+
+  // Destroys the buffer of APP1 segment if exists.
+  void DestroyApp1();
+
+  // The buffer pointer of yuv image (YU12). Not owned by this class.
+  const uint8_t* yu12_buffer_;
+  // The size of yuv image.
+  uint16_t yu12_width_;
+  uint16_t yu12_height_;
+
+  // The size of thumbnail.
+  uint16_t thumbnail_width_;
+  uint16_t thumbnail_height_;
+
+  // The Exif data (APP1). Owned by this class.
+  ExifData* exif_data_;
+  // The raw data of APP1 segment. It's allocated by ExifMem in |exif_data_| but
+  // owned by this class.
+  uint8_t* app1_buffer_;
+  // The length of |app1_buffer_|.
+  unsigned int app1_length_;
+  // The quality of compressed thumbnail image. The size of EXIF thumbnail has
+  // to be smaller than 64KB. If quality is 100, the size may be bigger than
+  // 64KB.
+  int thumbnail_jpeg_quality_;
+
+  // The YU12 to Jpeg compressor.
+  JpegCompressor compressor_;
+};
+
+}  // namespace arc
+
+#endif  // INCLUDE_ARC_EXIF_UTILS_H_
diff --git a/modules/camera/3_4/arc/frame_buffer.cpp b/modules/camera/3_4/arc/frame_buffer.cpp
new file mode 100644
index 0000000..4ae0fe3
--- /dev/null
+++ b/modules/camera/3_4/arc/frame_buffer.cpp
@@ -0,0 +1,188 @@
+/* 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/frame_buffer.h"
+
+#include <sys/mman.h>
+
+#include <utility>
+
+#include "arc/common.h"
+#include "arc/image_processor.h"
+
+namespace arc {
+
+FrameBuffer::FrameBuffer()
+    : data_(nullptr),
+      data_size_(0),
+      buffer_size_(0),
+      width_(0),
+      height_(0),
+      fourcc_(0) {}
+
+FrameBuffer::~FrameBuffer() {}
+
+int FrameBuffer::SetDataSize(size_t data_size) {
+  if (data_size > buffer_size_) {
+    LOGF(ERROR) << "Buffer overflow: Buffer only has " << buffer_size_
+                << ", but data needs " << data_size;
+    return -EINVAL;
+  }
+  data_size_ = data_size;
+  return 0;
+}
+
+AllocatedFrameBuffer::AllocatedFrameBuffer(int buffer_size) {
+  buffer_.reset(new uint8_t[buffer_size]);
+  buffer_size_ = buffer_size;
+  data_ = buffer_.get();
+}
+
+AllocatedFrameBuffer::AllocatedFrameBuffer(uint8_t* buffer, int buffer_size) {
+  buffer_.reset(buffer);
+  buffer_size_ = buffer_size;
+  data_ = buffer_.get();
+}
+
+AllocatedFrameBuffer::~AllocatedFrameBuffer() {}
+
+int AllocatedFrameBuffer::SetDataSize(size_t size) {
+  if (size > buffer_size_) {
+    buffer_.reset(new uint8_t[size]);
+    buffer_size_ = size;
+    data_ = buffer_.get();
+  }
+  data_size_ = size;
+  return 0;
+}
+
+void AllocatedFrameBuffer::Reset() { memset(data_, 0, buffer_size_); }
+
+V4L2FrameBuffer::V4L2FrameBuffer(base::ScopedFD fd, int buffer_size,
+                                 uint32_t width, uint32_t height,
+                                 uint32_t fourcc)
+    : fd_(std::move(fd)), is_mapped_(false) {
+  buffer_size_ = buffer_size;
+  width_ = width;
+  height_ = height;
+  fourcc_ = fourcc;
+}
+
+V4L2FrameBuffer::~V4L2FrameBuffer() {
+  if (Unmap()) {
+    LOGF(ERROR) << "Unmap failed";
+  }
+}
+
+int V4L2FrameBuffer::Map() {
+  base::AutoLock l(lock_);
+  if (is_mapped_) {
+    LOGF(ERROR) << "The buffer is already mapped";
+    return -EINVAL;
+  }
+  void* addr = mmap(NULL, buffer_size_, PROT_READ, MAP_SHARED, fd_.get(), 0);
+  if (addr == MAP_FAILED) {
+    LOGF(ERROR) << "mmap() failed: " << strerror(errno);
+    return -EINVAL;
+  }
+  data_ = static_cast<uint8_t*>(addr);
+  is_mapped_ = true;
+  return 0;
+}
+
+int V4L2FrameBuffer::Unmap() {
+  base::AutoLock l(lock_);
+  if (is_mapped_ && munmap(data_, buffer_size_)) {
+    LOGF(ERROR) << "mummap() failed: " << strerror(errno);
+    return -EINVAL;
+  }
+  is_mapped_ = false;
+  return 0;
+}
+
+GrallocFrameBuffer::GrallocFrameBuffer(buffer_handle_t buffer, uint32_t width,
+                                       uint32_t height, uint32_t fourcc,
+                                       uint32_t device_buffer_length,
+                                       uint32_t stream_usage)
+    : buffer_(buffer),
+      is_mapped_(false),
+      device_buffer_length_(device_buffer_length),
+      stream_usage_(stream_usage) {
+  const hw_module_t* module = nullptr;
+  int ret = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
+  if (ret || !module) {
+    LOGF(ERROR) << "Failed to get gralloc module.";
+    return;
+  }
+  gralloc_module_ = reinterpret_cast<const gralloc_module_t*>(module);
+  width_ = width;
+  height_ = height;
+  fourcc_ = fourcc;
+}
+
+GrallocFrameBuffer::~GrallocFrameBuffer() {
+  if (Unmap()) {
+    LOGF(ERROR) << "Unmap failed";
+  }
+}
+
+int GrallocFrameBuffer::Map() {
+  base::AutoLock l(lock_);
+  if (is_mapped_) {
+    LOGF(ERROR) << "The buffer is already mapped";
+    return -EINVAL;
+  }
+
+  void* addr;
+  int ret = 0;
+  switch (fourcc_) {
+    case V4L2_PIX_FMT_YUV420:
+    case V4L2_PIX_FMT_YVU420:
+    case V4L2_PIX_FMT_YUYV:
+      android_ycbcr yuv_data;
+      ret = gralloc_module_->lock_ycbcr(gralloc_module_, buffer_, stream_usage_,
+                                        0, 0, width_, height_, &yuv_data);
+      addr = yuv_data.y;
+      break;
+    case V4L2_PIX_FMT_JPEG:
+      ret = gralloc_module_->lock(gralloc_module_, buffer_, stream_usage_, 0, 0,
+                                  device_buffer_length_, 1, &addr);
+      break;
+    case V4L2_PIX_FMT_BGR32:
+    case V4L2_PIX_FMT_RGB32:
+      ret = gralloc_module_->lock(gralloc_module_, buffer_, stream_usage_, 0, 0,
+                                  width_, height_, &addr);
+      break;
+    default:
+      return -EINVAL;
+  }
+
+  if (ret) {
+    LOGF(ERROR) << "Failed to gralloc lock buffer: " << ret;
+    return ret;
+  }
+
+  data_ = static_cast<uint8_t*>(addr);
+  if (fourcc_ == V4L2_PIX_FMT_YVU420 || fourcc_ == V4L2_PIX_FMT_YUV420 ||
+      fourcc_ == V4L2_PIX_FMT_NV21 || fourcc_ == V4L2_PIX_FMT_RGB32 ||
+      fourcc_ == V4L2_PIX_FMT_BGR32) {
+    buffer_size_ = ImageProcessor::GetConvertedSize(fourcc_, width_, height_);
+  }
+
+  is_mapped_ = true;
+  return 0;
+}
+
+int GrallocFrameBuffer::Unmap() {
+  base::AutoLock l(lock_);
+  if (is_mapped_ && gralloc_module_->unlock(gralloc_module_, buffer_)) {
+    LOGF(ERROR) << "Failed to unmap buffer: ";
+    return -EINVAL;
+  }
+  is_mapped_ = false;
+  return 0;
+}
+
+}  // namespace arc
diff --git a/modules/camera/3_4/arc/frame_buffer.h b/modules/camera/3_4/arc/frame_buffer.h
new file mode 100644
index 0000000..3efeb3b
--- /dev/null
+++ b/modules/camera/3_4/arc/frame_buffer.h
@@ -0,0 +1,135 @@
+/* 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.
+ */
+
+#ifndef HAL_USB_FRAME_BUFFER_H_
+#define HAL_USB_FRAME_BUFFER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <base/files/scoped_file.h>
+#include <base/synchronization/lock.h>
+
+#include <hardware/gralloc.h>
+
+namespace arc {
+
+class FrameBuffer {
+ public:
+  FrameBuffer();
+  virtual ~FrameBuffer();
+
+  // If mapped successfully, the address will be assigned to |data_| and return
+  // 0. Otherwise, returns -EINVAL.
+  virtual int Map() = 0;
+
+  // Unmaps the mapped address. Returns 0 for success.
+  virtual int Unmap() = 0;
+
+  uint8_t* GetData() const { return data_; }
+  size_t GetDataSize() const { return data_size_; }
+  size_t GetBufferSize() const { return buffer_size_; }
+  uint32_t GetWidth() const { return width_; }
+  uint32_t GetHeight() const { return height_; }
+  uint32_t GetFourcc() const { return fourcc_; }
+
+  void SetFourcc(uint32_t fourcc) { fourcc_ = fourcc; }
+  virtual int SetDataSize(size_t data_size);
+
+ protected:
+  uint8_t* data_;
+
+  // The number of bytes used in the buffer.
+  size_t data_size_;
+
+  // The number of bytes allocated in the buffer.
+  size_t buffer_size_;
+
+  // Frame resolution.
+  uint32_t width_;
+  uint32_t height_;
+
+  // This is V4L2_PIX_FMT_* in linux/videodev2.h.
+  uint32_t fourcc_;
+};
+
+// AllocatedFrameBuffer is used for the buffer from hal malloc-ed. User should
+// be aware to manage the memory.
+class AllocatedFrameBuffer : public FrameBuffer {
+ public:
+  explicit AllocatedFrameBuffer(int buffer_size);
+  explicit AllocatedFrameBuffer(uint8_t* buffer, int buffer_size);
+  ~AllocatedFrameBuffer() override;
+
+  // No-op for the two functions.
+  int Map() override { return 0; }
+  int Unmap() override { return 0; }
+
+  void SetWidth(uint32_t width) { width_ = width; }
+  void SetHeight(uint32_t height) { height_ = height; }
+  int SetDataSize(size_t data_size) override;
+  void Reset();
+
+ private:
+  std::unique_ptr<uint8_t[]> buffer_;
+};
+
+// V4L2FrameBuffer is used for the buffer from V4L2CameraDevice. Maps the fd
+// in constructor. Unmaps and closes the fd in destructor.
+class V4L2FrameBuffer : public FrameBuffer {
+ public:
+  V4L2FrameBuffer(base::ScopedFD fd, int buffer_size, uint32_t width,
+                  uint32_t height, uint32_t fourcc);
+  // Unmaps |data_| and closes |fd_|.
+  ~V4L2FrameBuffer();
+
+  int Map() override;
+  int Unmap() override;
+  int GetFd() const { return fd_.get(); }
+
+ private:
+  // File descriptor of V4L2 frame buffer.
+  base::ScopedFD fd_;
+
+  bool is_mapped_;
+
+  // Lock to guard |is_mapped_|.
+  base::Lock lock_;
+};
+
+// GrallocFrameBuffer is used for the buffer from Android framework. Uses
+// CameraBufferMapper to lock and unlock the buffer.
+class GrallocFrameBuffer : public FrameBuffer {
+ public:
+  GrallocFrameBuffer(buffer_handle_t buffer, uint32_t width, uint32_t height,
+                     uint32_t fourcc, uint32_t device_buffer_length,
+                     uint32_t stream_usage);
+  ~GrallocFrameBuffer();
+
+  int Map() override;
+  int Unmap() override;
+
+ private:
+  // The currently used buffer for |buffer_mapper_| operations.
+  buffer_handle_t buffer_;
+
+  // Used to import gralloc buffer.
+  const gralloc_module_t* gralloc_module_;
+
+  bool is_mapped_;
+
+  // Lock to guard |is_mapped_|.
+  base::Lock lock_;
+
+  // Camera stream and device buffer context.
+  uint32_t device_buffer_length_;
+  uint32_t stream_usage_;
+};
+
+}  // namespace arc
+
+#endif  // HAL_USB_FRAME_BUFFER_H_
diff --git a/modules/camera/3_4/arc/image_processor.cpp b/modules/camera/3_4/arc/image_processor.cpp
new file mode 100644
index 0000000..f0fee91
--- /dev/null
+++ b/modules/camera/3_4/arc/image_processor.cpp
@@ -0,0 +1,491 @@
+/* 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/image_processor.h"
+
+#include <errno.h>
+#include <libyuv.h>
+#include <time.h>
+
+#include "arc/common.h"
+#include "arc/common_types.h"
+#include "arc/exif_utils.h"
+#include "arc/jpeg_compressor.h"
+
+namespace arc {
+
+using android::CameraMetadata;
+
+/*
+ * Formats have different names in different header files. Here is the mapping
+ * table:
+ *
+ * android_pixel_format_t          videodev2.h           FOURCC in libyuv
+ * -----------------------------------------------------------------------------
+ * HAL_PIXEL_FORMAT_YV12         = V4L2_PIX_FMT_YVU420 = FOURCC_YV12
+ * HAL_PIXEL_FORMAT_YCrCb_420_SP = V4L2_PIX_FMT_NV21   = FOURCC_NV21
+ * HAL_PIXEL_FORMAT_RGBA_8888    = V4L2_PIX_FMT_RGB32  = FOURCC_BGR4
+ * HAL_PIXEL_FORMAT_YCbCr_422_I  = V4L2_PIX_FMT_YUYV   = FOURCC_YUYV
+ *                                                     = FOURCC_YUY2
+ *                                 V4L2_PIX_FMT_YUV420 = FOURCC_I420
+ *                                                     = FOURCC_YU12
+ *                                 V4L2_PIX_FMT_MJPEG  = FOURCC_MJPG
+ *
+ * Camera device generates FOURCC_YUYV and FOURCC_MJPG.
+ * Preview needs FOURCC_ARGB format.
+ * Software video encoder needs FOURCC_YU12.
+ * CTS requires FOURCC_YV12 and FOURCC_NV21 for applications.
+ *
+ * Android stride requirement:
+ * YV12 horizontal stride should be a multiple of 16 pixels. See
+ * android.graphics.ImageFormat.YV12.
+ * The stride of ARGB, YU12, and NV21 are always equal to the width.
+ *
+ * Conversion Path:
+ * MJPG/YUYV (from camera) -> YU12 -> ARGB (preview)
+ *                                 -> NV21 (apps)
+ *                                 -> YV12 (apps)
+ *                                 -> YU12 (video encoder)
+ */
+
+// YV12 horizontal stride should be a multiple of 16 pixels for each plane.
+// |dst_stride_uv| is the pixel stride of u or v plane.
+static int YU12ToYV12(const void* yv12, void* yu12, int width, int height,
+                      int dst_stride_y, int dst_stride_uv);
+static int YU12ToNV21(const void* yv12, void* nv21, int width, int height);
+static bool ConvertToJpeg(const CameraMetadata& metadata,
+                          const FrameBuffer& in_frame, FrameBuffer* out_frame);
+static bool SetExifTags(const CameraMetadata& metadata, ExifUtils* utils);
+
+// How precise the float-to-rational conversion for EXIF tags would be.
+static const int kRationalPrecision = 10000;
+
+// Default JPEG quality settings.
+static const int DEFAULT_JPEG_QUALITY = 80;
+
+inline static size_t Align16(size_t value) { return (value + 15) & ~15; }
+
+size_t ImageProcessor::GetConvertedSize(int fourcc, uint32_t width,
+                                        uint32_t height) {
+  if ((width % 2) || (height % 2)) {
+    LOGF(ERROR) << "Width or height is not even (" << width << " x " << height
+                << ")";
+    return 0;
+  }
+
+  switch (fourcc) {
+    case V4L2_PIX_FMT_YVU420:  // YV12
+      return Align16(width) * height + Align16(width / 2) * height;
+    case V4L2_PIX_FMT_YUV420:  // YU12
+    // Fall-through.
+    case V4L2_PIX_FMT_NV21:  // NV21
+      return width * height * 3 / 2;
+    case V4L2_PIX_FMT_BGR32:
+    case V4L2_PIX_FMT_RGB32:
+      return width * height * 4;
+    default:
+      LOGF(ERROR) << "Pixel format " << FormatToString(fourcc)
+                  << " is unsupported.";
+      return 0;
+  }
+}
+
+bool ImageProcessor::SupportsConversion(uint32_t from_fourcc,
+                                        uint32_t to_fourcc) {
+  switch (from_fourcc) {
+    case V4L2_PIX_FMT_YUYV:
+      return (to_fourcc == V4L2_PIX_FMT_YUV420);
+    case V4L2_PIX_FMT_YUV420:
+      return (
+          to_fourcc == V4L2_PIX_FMT_YUV420 ||
+          to_fourcc == V4L2_PIX_FMT_YVU420 || to_fourcc == V4L2_PIX_FMT_NV21 ||
+          to_fourcc == V4L2_PIX_FMT_RGB32 || to_fourcc == V4L2_PIX_FMT_BGR32 ||
+          to_fourcc == V4L2_PIX_FMT_JPEG);
+    case V4L2_PIX_FMT_MJPEG:
+      return (to_fourcc == V4L2_PIX_FMT_YUV420);
+    default:
+      return false;
+  }
+}
+
+int ImageProcessor::ConvertFormat(const CameraMetadata& metadata,
+                                  const FrameBuffer& in_frame,
+                                  FrameBuffer* out_frame) {
+  if ((in_frame.GetWidth() % 2) || (in_frame.GetHeight() % 2)) {
+    LOGF(ERROR) << "Width or height is not even (" << in_frame.GetWidth()
+                << " x " << in_frame.GetHeight() << ")";
+    return -EINVAL;
+  }
+
+  size_t data_size = GetConvertedSize(
+      out_frame->GetFourcc(), in_frame.GetWidth(), in_frame.GetHeight());
+
+  if (out_frame->SetDataSize(data_size)) {
+    LOGF(ERROR) << "Set data size failed";
+    return -EINVAL;
+  }
+
+  if (in_frame.GetFourcc() == V4L2_PIX_FMT_YUYV) {
+    switch (out_frame->GetFourcc()) {
+      case V4L2_PIX_FMT_YUV420:  // YU12
+      {
+        int res = libyuv::YUY2ToI420(
+            in_frame.GetData(),      /* src_yuy2 */
+            in_frame.GetWidth() * 2, /* src_stride_yuy2 */
+            out_frame->GetData(),    /* dst_y */
+            out_frame->GetWidth(),   /* dst_stride_y */
+            out_frame->GetData() +
+                out_frame->GetWidth() * out_frame->GetHeight(), /* dst_u */
+            out_frame->GetWidth() / 2, /* dst_stride_u */
+            out_frame->GetData() + out_frame->GetWidth() *
+                                       out_frame->GetHeight() * 5 /
+                                       4, /* dst_v */
+            out_frame->GetWidth() / 2,    /* dst_stride_v */
+            in_frame.GetWidth(), in_frame.GetHeight());
+        LOGF_IF(ERROR, res) << "YUY2ToI420() for YU12 returns " << res;
+        return res ? -EINVAL : 0;
+      }
+      default:
+        LOGF(ERROR) << "Destination pixel format "
+                    << FormatToString(out_frame->GetFourcc())
+                    << " is unsupported for YUYV source format.";
+        return -EINVAL;
+    }
+  } else if (in_frame.GetFourcc() == V4L2_PIX_FMT_YUV420) {
+    // V4L2_PIX_FMT_YVU420 is YV12. I420 is usually referred to YU12
+    // (V4L2_PIX_FMT_YUV420), and YV12 is similar to YU12 except that U/V
+    // planes are swapped.
+    switch (out_frame->GetFourcc()) {
+      case V4L2_PIX_FMT_YVU420:  // YV12
+      {
+        int ystride = Align16(in_frame.GetWidth());
+        int uvstride = Align16(in_frame.GetWidth() / 2);
+        int res = YU12ToYV12(in_frame.GetData(), out_frame->GetData(),
+                             in_frame.GetWidth(), in_frame.GetHeight(), ystride,
+                             uvstride);
+        LOGF_IF(ERROR, res) << "YU12ToYV12() returns " << res;
+        return res ? -EINVAL : 0;
+      }
+      case V4L2_PIX_FMT_YUV420:  // YU12
+      {
+        memcpy(out_frame->GetData(), in_frame.GetData(),
+               in_frame.GetDataSize());
+        return 0;
+      }
+      case V4L2_PIX_FMT_NV21:  // NV21
+      {
+        // TODO(henryhsu): Use libyuv::I420ToNV21.
+        int res = YU12ToNV21(in_frame.GetData(), out_frame->GetData(),
+                             in_frame.GetWidth(), in_frame.GetHeight());
+        LOGF_IF(ERROR, res) << "YU12ToNV21() returns " << res;
+        return res ? -EINVAL : 0;
+      }
+      case V4L2_PIX_FMT_BGR32: {
+        int res = libyuv::I420ToABGR(
+            in_frame.GetData(),  /* src_y */
+            in_frame.GetWidth(), /* src_stride_y */
+            in_frame.GetData() +
+                in_frame.GetWidth() * in_frame.GetHeight(), /* src_u */
+            in_frame.GetWidth() / 2,                        /* src_stride_u */
+            in_frame.GetData() +
+                in_frame.GetWidth() * in_frame.GetHeight() * 5 / 4, /* src_v */
+            in_frame.GetWidth() / 2,   /* src_stride_v */
+            out_frame->GetData(),      /* dst_abgr */
+            out_frame->GetWidth() * 4, /* dst_stride_abgr */
+            in_frame.GetWidth(), in_frame.GetHeight());
+        LOGF_IF(ERROR, res) << "I420ToABGR() returns " << res;
+        return res ? -EINVAL : 0;
+      }
+      case V4L2_PIX_FMT_RGB32: {
+        int res = libyuv::I420ToARGB(
+            in_frame.GetData(),  /* src_y */
+            in_frame.GetWidth(), /* src_stride_y */
+            in_frame.GetData() +
+                in_frame.GetWidth() * in_frame.GetHeight(), /* src_u */
+            in_frame.GetWidth() / 2,                        /* src_stride_u */
+            in_frame.GetData() +
+                in_frame.GetWidth() * in_frame.GetHeight() * 5 / 4, /* src_v */
+            in_frame.GetWidth() / 2,   /* src_stride_v */
+            out_frame->GetData(),      /* dst_argb */
+            out_frame->GetWidth() * 4, /* dst_stride_argb */
+            in_frame.GetWidth(), in_frame.GetHeight());
+        LOGF_IF(ERROR, res) << "I420ToARGB() returns " << res;
+        return res ? -EINVAL : 0;
+      }
+      case V4L2_PIX_FMT_JPEG: {
+        bool res = ConvertToJpeg(metadata, in_frame, out_frame);
+        LOGF_IF(ERROR, !res) << "ConvertToJpeg() returns " << res;
+        return res ? -EINVAL : 0;
+      }
+      default:
+        LOGF(ERROR) << "Destination pixel format "
+                    << FormatToString(out_frame->GetFourcc())
+                    << " is unsupported for YU12 source format.";
+        return -EINVAL;
+    }
+  } else if (in_frame.GetFourcc() == V4L2_PIX_FMT_MJPEG) {
+    switch (out_frame->GetFourcc()) {
+      case V4L2_PIX_FMT_YUV420:  // YU12
+      {
+        int res = libyuv::MJPGToI420(
+            in_frame.GetData(),     /* sample */
+            in_frame.GetDataSize(), /* sample_size */
+            out_frame->GetData(),   /* dst_y */
+            out_frame->GetWidth(),  /* dst_stride_y */
+            out_frame->GetData() +
+                out_frame->GetWidth() * out_frame->GetHeight(), /* dst_u */
+            out_frame->GetWidth() / 2, /* dst_stride_u */
+            out_frame->GetData() + out_frame->GetWidth() *
+                                       out_frame->GetHeight() * 5 /
+                                       4, /* dst_v */
+            out_frame->GetWidth() / 2,    /* dst_stride_v */
+            in_frame.GetWidth(), in_frame.GetHeight(), out_frame->GetWidth(),
+            out_frame->GetHeight());
+        LOGF_IF(ERROR, res) << "MJPEGToI420() returns " << res;
+        return res ? -EINVAL : 0;
+      }
+      default:
+        LOGF(ERROR) << "Destination pixel format "
+                    << FormatToString(out_frame->GetFourcc())
+                    << " is unsupported for MJPEG source format.";
+        return -EINVAL;
+    }
+  } else {
+    LOGF(ERROR) << "Convert format doesn't support source format "
+                << FormatToString(in_frame.GetFourcc());
+    return -EINVAL;
+  }
+}
+
+int ImageProcessor::Scale(const FrameBuffer& in_frame, FrameBuffer* out_frame) {
+  if (in_frame.GetFourcc() != V4L2_PIX_FMT_YUV420) {
+    LOGF(ERROR) << "Pixel format " << FormatToString(in_frame.GetFourcc())
+                << " is unsupported.";
+    return -EINVAL;
+  }
+
+  size_t data_size = GetConvertedSize(
+      in_frame.GetFourcc(), out_frame->GetWidth(), out_frame->GetHeight());
+
+  if (out_frame->SetDataSize(data_size)) {
+    LOGF(ERROR) << "Set data size failed";
+    return -EINVAL;
+  }
+  out_frame->SetFourcc(in_frame.GetFourcc());
+
+  VLOGF(1) << "Scale image from " << in_frame.GetWidth() << "x"
+           << in_frame.GetHeight() << " to " << out_frame->GetWidth() << "x"
+           << out_frame->GetHeight();
+
+  int ret = libyuv::I420Scale(
+      in_frame.GetData(), in_frame.GetWidth(),
+      in_frame.GetData() + in_frame.GetWidth() * in_frame.GetHeight(),
+      in_frame.GetWidth() / 2,
+      in_frame.GetData() + in_frame.GetWidth() * in_frame.GetHeight() * 5 / 4,
+      in_frame.GetWidth() / 2, in_frame.GetWidth(), in_frame.GetHeight(),
+      out_frame->GetData(), out_frame->GetWidth(),
+      out_frame->GetData() + out_frame->GetWidth() * out_frame->GetHeight(),
+      out_frame->GetWidth() / 2,
+      out_frame->GetData() +
+          out_frame->GetWidth() * out_frame->GetHeight() * 5 / 4,
+      out_frame->GetWidth() / 2, out_frame->GetWidth(), out_frame->GetHeight(),
+      libyuv::FilterMode::kFilterNone);
+  LOGF_IF(ERROR, ret) << "I420Scale failed: " << ret;
+  return ret;
+}
+
+static int YU12ToYV12(const void* yu12, void* yv12, int width, int height,
+                      int dst_stride_y, int dst_stride_uv) {
+  if ((width % 2) || (height % 2)) {
+    LOGF(ERROR) << "Width or height is not even (" << width << " x " << height
+                << ")";
+    return -EINVAL;
+  }
+  if (dst_stride_y < width || dst_stride_uv < width / 2) {
+    LOGF(ERROR) << "Y plane stride (" << dst_stride_y
+                << ") or U/V plane stride (" << dst_stride_uv
+                << ") is invalid for width " << width;
+    return -EINVAL;
+  }
+
+  const uint8_t* src = reinterpret_cast<const uint8_t*>(yu12);
+  uint8_t* dst = reinterpret_cast<uint8_t*>(yv12);
+  const uint8_t* u_src = src + width * height;
+  uint8_t* u_dst = dst + dst_stride_y * height + dst_stride_uv * height / 2;
+  const uint8_t* v_src = src + width * height * 5 / 4;
+  uint8_t* v_dst = dst + dst_stride_y * height;
+
+  return libyuv::I420Copy(src, width, u_src, width / 2, v_src, width / 2, dst,
+                          dst_stride_y, u_dst, dst_stride_uv, v_dst,
+                          dst_stride_uv, width, height);
+}
+
+static int YU12ToNV21(const void* yu12, void* nv21, int width, int height) {
+  if ((width % 2) || (height % 2)) {
+    LOGF(ERROR) << "Width or height is not even (" << width << " x " << height
+                << ")";
+    return -EINVAL;
+  }
+
+  const uint8_t* src = reinterpret_cast<const uint8_t*>(yu12);
+  uint8_t* dst = reinterpret_cast<uint8_t*>(nv21);
+  const uint8_t* u_src = src + width * height;
+  const uint8_t* v_src = src + width * height * 5 / 4;
+  uint8_t* vu_dst = dst + width * height;
+
+  memcpy(dst, src, width * height);
+
+  for (int i = 0; i < height / 2; i++) {
+    for (int j = 0; j < width / 2; j++) {
+      *vu_dst++ = *v_src++;
+      *vu_dst++ = *u_src++;
+    }
+  }
+  return 0;
+}
+
+static bool ConvertToJpeg(const CameraMetadata& metadata,
+                          const FrameBuffer& in_frame, FrameBuffer* out_frame) {
+  ExifUtils utils;
+  int jpeg_quality, thumbnail_jpeg_quality;
+  camera_metadata_ro_entry entry;
+
+  if (metadata.exists(ANDROID_JPEG_QUALITY)) {
+    entry = metadata.find(ANDROID_JPEG_QUALITY);
+    jpeg_quality = entry.data.u8[0];
+  } else {
+    LOGF(ERROR) << "Could not find jpeg quality in metadata, defaulting to "
+                << DEFAULT_JPEG_QUALITY;
+    jpeg_quality = DEFAULT_JPEG_QUALITY;
+  }
+  if (metadata.exists(ANDROID_JPEG_THUMBNAIL_QUALITY)) {
+    entry = metadata.find(ANDROID_JPEG_THUMBNAIL_QUALITY);
+    thumbnail_jpeg_quality = entry.data.u8[0];
+  } else {
+    thumbnail_jpeg_quality = jpeg_quality;
+  }
+
+  if (!utils.Initialize(in_frame.GetData(), in_frame.GetWidth(),
+                        in_frame.GetHeight(), thumbnail_jpeg_quality)) {
+    LOGF(ERROR) << "ExifUtils initialization failed.";
+    return false;
+  }
+  if (!SetExifTags(metadata, &utils)) {
+    LOGF(ERROR) << "Setting Exif tags failed.";
+    return false;
+  }
+  if (!utils.GenerateApp1()) {
+    LOGF(ERROR) << "Generating APP1 segment failed.";
+    return false;
+  }
+  JpegCompressor compressor;
+  if (!compressor.CompressImage(in_frame.GetData(), in_frame.GetWidth(),
+                                in_frame.GetHeight(), jpeg_quality,
+                                utils.GetApp1Buffer(), utils.GetApp1Length())) {
+    LOGF(ERROR) << "JPEG image compression failed";
+    return false;
+  }
+  size_t buffer_length = compressor.GetCompressedImageSize();
+  memcpy(out_frame->GetData(), compressor.GetCompressedImagePtr(),
+         buffer_length);
+  return true;
+}
+
+static bool SetExifTags(const CameraMetadata& metadata, ExifUtils* utils) {
+  time_t raw_time = 0;
+  struct tm time_info;
+  bool time_available = time(&raw_time) != -1;
+  localtime_r(&raw_time, &time_info);
+  if (!utils->SetDateTime(time_info)) {
+    LOGF(ERROR) << "Setting data time failed.";
+    return false;
+  }
+
+  float focal_length;
+  camera_metadata_ro_entry entry = metadata.find(ANDROID_LENS_FOCAL_LENGTH);
+  if (entry.count) {
+    focal_length = entry.data.f[0];
+  } else {
+    LOGF(ERROR) << "Cannot find focal length in metadata.";
+    return false;
+  }
+  if (!utils->SetFocalLength(
+          static_cast<uint32_t>(focal_length * kRationalPrecision),
+          kRationalPrecision)) {
+    LOGF(ERROR) << "Setting focal length failed.";
+    return false;
+  }
+
+  if (metadata.exists(ANDROID_JPEG_GPS_COORDINATES)) {
+    entry = 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 (metadata.exists(ANDROID_JPEG_GPS_PROCESSING_METHOD)) {
+    entry = 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 && metadata.exists(ANDROID_JPEG_GPS_TIMESTAMP)) {
+    entry = metadata.find(ANDROID_JPEG_GPS_TIMESTAMP);
+    time_t timestamp = static_cast<time_t>(entry.data.i64[0]);
+    if (gmtime_r(&timestamp, &time_info)) {
+      if (!utils->SetGpsTimestamp(time_info)) {
+        LOGF(ERROR) << "Setting gps timestamp failed.";
+        return false;
+      }
+    } else {
+      LOGF(ERROR) << "Time tranformation failed.";
+      return false;
+    }
+  }
+
+  if (metadata.exists(ANDROID_JPEG_ORIENTATION)) {
+    entry = metadata.find(ANDROID_JPEG_ORIENTATION);
+    if (!utils->SetOrientation(entry.data.i32[0])) {
+      LOGF(ERROR) << "Setting orientation failed.";
+      return false;
+    }
+  }
+
+  if (metadata.exists(ANDROID_JPEG_THUMBNAIL_SIZE)) {
+    entry = metadata.find(ANDROID_JPEG_THUMBNAIL_SIZE);
+    if (entry.count < 2) {
+      LOGF(ERROR) << "Thumbnail size in metadata is not complete.";
+      return false;
+    }
+    int thumbnail_width = entry.data.i32[0];
+    int thumbnail_height = entry.data.i32[1];
+    if (thumbnail_width > 0 && thumbnail_height > 0) {
+      if (!utils->SetThumbnailSize(static_cast<uint16_t>(thumbnail_width),
+                                   static_cast<uint16_t>(thumbnail_height))) {
+        LOGF(ERROR) << "Setting thumbnail size failed.";
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+}  // namespace arc
diff --git a/modules/camera/3_4/arc/image_processor.h b/modules/camera/3_4/arc/image_processor.h
new file mode 100644
index 0000000..323680a
--- /dev/null
+++ b/modules/camera/3_4/arc/image_processor.h
@@ -0,0 +1,48 @@
+/* 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.
+ */
+
+#ifndef HAL_USB_IMAGE_PROCESSOR_H_
+#define HAL_USB_IMAGE_PROCESSOR_H_
+
+#include <string>
+
+// FourCC pixel formats (defined as V4L2_PIX_FMT_*).
+#include <linux/videodev2.h>
+// Declarations of HAL_PIXEL_FORMAT_XXX.
+#include <system/graphics.h>
+
+#include <camera/CameraMetadata.h>
+#include "frame_buffer.h"
+
+namespace arc {
+
+// V4L2_PIX_FMT_YVU420(YV12) in ImageProcessor has alignment requirement.
+// The stride of Y, U, and V planes should a multiple of 16 pixels.
+struct ImageProcessor {
+  // Calculate the output buffer size when converting to the specified pixel
+  // format. |fourcc| is defined as V4L2_PIX_FMT_* in linux/videodev2.h.
+  // Return 0 on error.
+  static size_t GetConvertedSize(int fourcc, uint32_t width, uint32_t height);
+
+  // Return whether this class supports the provided conversion.
+  static bool SupportsConversion(uint32_t from_fourcc, uint32_t to_fourcc);
+
+  // Convert format from |in_frame.fourcc| to |out_frame->fourcc|. Caller should
+  // fill |data|, |buffer_size|, |width|, and |height| of |out_frame|. The
+  // function will fill |out_frame->data_size|. Return non-zero error code on
+  // failure; return 0 on success.
+  static int ConvertFormat(const android::CameraMetadata& metadata,
+                           const FrameBuffer& in_frame, FrameBuffer* out_frame);
+
+  // Scale image size according to |in_frame| and |out_frame|. Only support
+  // V4L2_PIX_FMT_YUV420 format. Caller should fill |data|, |width|, |height|,
+  // and |buffer_size| of |out_frame|. The function will fill |data_size| and
+  // |fourcc| of |out_frame|.
+  static int Scale(const FrameBuffer& in_frame, FrameBuffer* out_frame);
+};
+
+}  // namespace arc
+
+#endif  // HAL_USB_IMAGE_PROCESSOR_H_
diff --git a/modules/camera/3_4/arc/jpeg_compressor.cpp b/modules/camera/3_4/arc/jpeg_compressor.cpp
new file mode 100644
index 0000000..7c61b40
--- /dev/null
+++ b/modules/camera/3_4/arc/jpeg_compressor.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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/jpeg_compressor.h"
+
+#include <memory>
+
+#include <errno.h>
+
+#include "arc/common.h"
+
+namespace arc {
+
+// The destination manager that can access |result_buffer_| in JpegCompressor.
+struct destination_mgr {
+ public:
+  struct jpeg_destination_mgr mgr;
+  JpegCompressor* compressor;
+};
+
+JpegCompressor::JpegCompressor() {}
+
+JpegCompressor::~JpegCompressor() {}
+
+bool JpegCompressor::CompressImage(const void* image, int width, int height,
+                                   int quality, const void* app1Buffer,
+                                   unsigned int app1Size) {
+  if (width % 8 != 0 || height % 2 != 0) {
+    LOGF(ERROR) << "Image size can not be handled: " << width << "x" << height;
+    return false;
+  }
+
+  result_buffer_.clear();
+  if (!Encode(image, width, height, quality, app1Buffer, app1Size)) {
+    return false;
+  }
+  LOGF(INFO) << "Compressed JPEG: " << (width * height * 12) / 8 << "[" << width
+             << "x" << height << "] -> " << result_buffer_.size() << " bytes";
+  return true;
+}
+
+const void* JpegCompressor::GetCompressedImagePtr() {
+  return result_buffer_.data();
+}
+
+size_t JpegCompressor::GetCompressedImageSize() {
+  return result_buffer_.size();
+}
+
+void JpegCompressor::InitDestination(j_compress_ptr cinfo) {
+  destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+  std::vector<JOCTET>& buffer = dest->compressor->result_buffer_;
+  buffer.resize(kBlockSize);
+  dest->mgr.next_output_byte = &buffer[0];
+  dest->mgr.free_in_buffer = buffer.size();
+}
+
+boolean JpegCompressor::EmptyOutputBuffer(j_compress_ptr cinfo) {
+  destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+  std::vector<JOCTET>& buffer = dest->compressor->result_buffer_;
+  size_t oldsize = buffer.size();
+  buffer.resize(oldsize + kBlockSize);
+  dest->mgr.next_output_byte = &buffer[oldsize];
+  dest->mgr.free_in_buffer = kBlockSize;
+  return true;
+}
+
+void JpegCompressor::TerminateDestination(j_compress_ptr cinfo) {
+  destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+  std::vector<JOCTET>& buffer = dest->compressor->result_buffer_;
+  buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
+}
+
+void JpegCompressor::OutputErrorMessage(j_common_ptr cinfo) {
+  char buffer[JMSG_LENGTH_MAX];
+
+  /* Create the message */
+  (*cinfo->err->format_message)(cinfo, buffer);
+  LOGF(ERROR) << buffer;
+}
+
+bool JpegCompressor::Encode(const void* inYuv, int width, int height,
+                            int jpegQuality, const void* app1Buffer,
+                            unsigned int app1Size) {
+  jpeg_compress_struct cinfo;
+  jpeg_error_mgr jerr;
+
+  cinfo.err = jpeg_std_error(&jerr);
+  // Override output_message() to print error log with ALOGE().
+  cinfo.err->output_message = &OutputErrorMessage;
+  jpeg_create_compress(&cinfo);
+  SetJpegDestination(&cinfo);
+
+  SetJpegCompressStruct(width, height, jpegQuality, &cinfo);
+  jpeg_start_compress(&cinfo, TRUE);
+
+  if (app1Buffer != nullptr && app1Size > 0) {
+    jpeg_write_marker(&cinfo, JPEG_APP0 + 1,
+                      static_cast<const JOCTET*>(app1Buffer), app1Size);
+  }
+
+  if (!Compress(&cinfo, static_cast<const uint8_t*>(inYuv))) {
+    return false;
+  }
+  jpeg_finish_compress(&cinfo);
+  return true;
+}
+
+void JpegCompressor::SetJpegDestination(jpeg_compress_struct* cinfo) {
+  destination_mgr* dest =
+      static_cast<struct destination_mgr*>((*cinfo->mem->alloc_small)(
+          (j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
+  dest->compressor = this;
+  dest->mgr.init_destination = &InitDestination;
+  dest->mgr.empty_output_buffer = &EmptyOutputBuffer;
+  dest->mgr.term_destination = &TerminateDestination;
+  cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
+}
+
+void JpegCompressor::SetJpegCompressStruct(int width, int height, int quality,
+                                           jpeg_compress_struct* cinfo) {
+  cinfo->image_width = width;
+  cinfo->image_height = height;
+  cinfo->input_components = 3;
+  cinfo->in_color_space = JCS_YCbCr;
+  jpeg_set_defaults(cinfo);
+
+  jpeg_set_quality(cinfo, quality, TRUE);
+  jpeg_set_colorspace(cinfo, JCS_YCbCr);
+  cinfo->raw_data_in = TRUE;
+  cinfo->dct_method = JDCT_IFAST;
+
+  // Configure sampling factors. The sampling factor is JPEG subsampling 420
+  // because the source format is YUV420.
+  cinfo->comp_info[0].h_samp_factor = 2;
+  cinfo->comp_info[0].v_samp_factor = 2;
+  cinfo->comp_info[1].h_samp_factor = 1;
+  cinfo->comp_info[1].v_samp_factor = 1;
+  cinfo->comp_info[2].h_samp_factor = 1;
+  cinfo->comp_info[2].v_samp_factor = 1;
+}
+
+bool JpegCompressor::Compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+  JSAMPROW y[kCompressBatchSize];
+  JSAMPROW cb[kCompressBatchSize / 2];
+  JSAMPROW cr[kCompressBatchSize / 2];
+  JSAMPARRAY planes[3]{y, cb, cr};
+
+  size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+  size_t uv_plane_size = y_plane_size / 4;
+  uint8_t* y_plane = const_cast<uint8_t*>(yuv);
+  uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
+  uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
+  std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+  memset(empty.get(), 0, cinfo->image_width);
+
+  while (cinfo->next_scanline < cinfo->image_height) {
+    for (int i = 0; i < kCompressBatchSize; ++i) {
+      size_t scanline = cinfo->next_scanline + i;
+      if (scanline < cinfo->image_height) {
+        y[i] = y_plane + scanline * cinfo->image_width;
+      } else {
+        y[i] = empty.get();
+      }
+    }
+    // cb, cr only have half scanlines
+    for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+      size_t scanline = cinfo->next_scanline / 2 + i;
+      if (scanline < cinfo->image_height / 2) {
+        int offset = scanline * (cinfo->image_width / 2);
+        cb[i] = u_plane + offset;
+        cr[i] = v_plane + offset;
+      } else {
+        cb[i] = cr[i] = empty.get();
+      }
+    }
+
+    int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
+    if (processed != kCompressBatchSize) {
+      LOGF(ERROR) << "Number of processed lines does not equal input lines.";
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace arc
diff --git a/modules/camera/3_4/arc/jpeg_compressor.h b/modules/camera/3_4/arc/jpeg_compressor.h
new file mode 100644
index 0000000..378f3cd
--- /dev/null
+++ b/modules/camera/3_4/arc/jpeg_compressor.h
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#ifndef INCLUDE_ARC_JPEG_COMPRESSOR_H_
+#define INCLUDE_ARC_JPEG_COMPRESSOR_H_
+
+// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
+#include <cstdio>
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+
+namespace arc {
+
+// Encapsulates a converter from YU12 to JPEG format. This class is not
+// thread-safe.
+class JpegCompressor {
+ public:
+  JpegCompressor();
+  ~JpegCompressor();
+
+  // Compresses YU12 image to JPEG format. After calling this method, call
+  // GetCompressedImagePtr() to get the image. |quality| is the resulted jpeg
+  // image quality. It ranges from 1 (poorest quality) to 100 (highest quality).
+  // |app1Buffer| is the buffer of APP1 segment (exif) which will be added to
+  // the compressed image. Returns false if errors occur during compression.
+  bool CompressImage(const void* image, int width, int height, int quality,
+                     const void* app1Buffer, unsigned int app1Size);
+
+  // Returns the compressed JPEG buffer pointer. This method must be called only
+  // after calling CompressImage().
+  const void* GetCompressedImagePtr();
+
+  // Returns the compressed JPEG buffer size. This method must be called only
+  // after calling CompressImage().
+  size_t GetCompressedImageSize();
+
+ private:
+  // InitDestination(), EmptyOutputBuffer() and TerminateDestination() are
+  // callback functions to be passed into jpeg library.
+  static void InitDestination(j_compress_ptr cinfo);
+  static boolean EmptyOutputBuffer(j_compress_ptr cinfo);
+  static void TerminateDestination(j_compress_ptr cinfo);
+  static void OutputErrorMessage(j_common_ptr cinfo);
+
+  // Returns false if errors occur.
+  bool Encode(const void* inYuv, int width, int height, int jpegQuality,
+              const void* app1Buffer, unsigned int app1Size);
+  void SetJpegDestination(jpeg_compress_struct* cinfo);
+  void SetJpegCompressStruct(int width, int height, int quality,
+                             jpeg_compress_struct* cinfo);
+  // Returns false if errors occur.
+  bool Compress(jpeg_compress_struct* cinfo, const uint8_t* yuv);
+
+  // The block size for encoded jpeg image buffer.
+  static const int kBlockSize = 16384;
+  // Process 16 lines of Y and 16 lines of U/V each time.
+  // We must pass at least 16 scanlines according to libjpeg documentation.
+  static const int kCompressBatchSize = 16;
+
+  // The buffer that holds the compressed result.
+  std::vector<JOCTET> result_buffer_;
+};
+
+}  // namespace arc
+
+#endif  // INCLUDE_ARC_JPEG_COMPRESSOR_H_
diff --git a/modules/camera/3_4/format_metadata_factory.cpp b/modules/camera/3_4/format_metadata_factory.cpp
index a08f9a8..db03678 100644
--- a/modules/camera/3_4/format_metadata_factory.cpp
+++ b/modules/camera/3_4/format_metadata_factory.cpp
@@ -16,6 +16,7 @@
 
 #include "format_metadata_factory.h"
 
+#include "arc/image_processor.h"
 #include "metadata/array_vector.h"
 #include "metadata/partial_metadata_factory.h"
 #include "metadata/property.h"
@@ -35,6 +36,7 @@
     HAL_LOGE("Failed to get device formats.");
     return res;
   }
+
   for (auto v4l2_format : v4l2_formats) {
     int32_t hal_format = StreamFormat::V4L2ToHalPixelFormat(v4l2_format);
     if (hal_format < 0) {
@@ -44,21 +46,17 @@
     result_formats->insert(hal_format);
   }
 
-  // In addition to well-defined formats, there may be an
-  // "Implementation Defined" format chosen by the HAL (in this
-  // case what that means is managed by the StreamFormat class).
-
-  // Get the V4L2 format for IMPLEMENTATION_DEFINED.
-  int v4l2_format = StreamFormat::HalToV4L2PixelFormat(
-      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
-  // If it's available, add IMPLEMENTATION_DEFINED to the result set.
-  if (v4l2_format && v4l2_formats.count(v4l2_format) > 0) {
-    result_formats->insert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
-  }
-
   return 0;
 }
 
+static int FpsRangesCompare(std::array<int32_t, 2> a,
+                            std::array<int32_t, 2> b) {
+  if (a[1] == b[1]) {
+    return a[0] > b[0];
+  }
+  return a[1] > b[1];
+}
+
 int AddFormatComponents(
     std::shared_ptr<V4L2Wrapper> device,
     std::insert_iterator<PartialMetadataSet> insertion_point) {
@@ -71,19 +69,39 @@
     return res;
   }
 
-  // Requirements check: need to support YCbCr_420_888, JPEG,
-  // and "Implementation Defined".
+  std::set<int32_t> unsupported_hal_formats;
   if (hal_formats.find(HAL_PIXEL_FORMAT_YCbCr_420_888) == hal_formats.end()) {
-    HAL_LOGE("YCbCr_420_888 not supported by device.");
-    return -ENODEV;
-  } else if (hal_formats.find(HAL_PIXEL_FORMAT_BLOB) == hal_formats.end()) {
-    HAL_LOGE("JPEG not supported by device.");
-    return -ENODEV;
-  } else if (hal_formats.find(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) ==
-             hal_formats.end()) {
-    HAL_LOGE("HAL implementation defined format not supported by device.");
-    return -ENODEV;
+    HAL_LOGW("YCbCr_420_888 (0x%x) not directly supported by device.",
+             HAL_PIXEL_FORMAT_YCbCr_420_888);
+    hal_formats.insert(HAL_PIXEL_FORMAT_YCbCr_420_888);
+    unsupported_hal_formats.insert(HAL_PIXEL_FORMAT_YCbCr_420_888);
   }
+  if (hal_formats.find(HAL_PIXEL_FORMAT_BLOB) == hal_formats.end()) {
+    HAL_LOGW("JPEG (0x%x) not directly supported by device.",
+             HAL_PIXEL_FORMAT_BLOB);
+    hal_formats.insert(HAL_PIXEL_FORMAT_BLOB);
+    unsupported_hal_formats.insert(HAL_PIXEL_FORMAT_BLOB);
+  }
+
+  // As hal_formats is populated by reading and converting V4L2 formats to the
+  // matching HAL formats, we will never see an implementation defined format in
+  // the list. We populate it ourselves and map it to a qualified format. If no
+  // qualified formats exist, this will be the first available format.
+  hal_formats.insert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+  unsupported_hal_formats.insert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+
+  // Qualified formats are the set of formats supported by this camera that the
+  // image processor can translate into the YU12 format. We additionally check
+  // that the conversion from YU12 to the desired hal format is supported.
+  std::vector<uint32_t> qualified_formats;
+  res = device->GetQualifiedFormats(&qualified_formats);
+  if (res && unsupported_hal_formats.size() > 1) {
+    HAL_LOGE(
+        "Failed to retrieve qualified formats, cannot perform conversions.");
+    return res;
+  }
+
+  HAL_LOGI("Supports %d qualified formats.", qualified_formats.size());
 
   // Find sizes and frame/stall durations for all formats.
   // We also want to find the smallest max frame duration amongst all formats,
@@ -96,7 +114,7 @@
   // Stall durations are {format, width, height, duration} (duration in ns).
   ArrayVector<int64_t, 4> stall_durations;
   int64_t min_max_frame_duration = std::numeric_limits<int64_t>::max();
-  int64_t max_min_frame_duration_yuv = std::numeric_limits<int64_t>::min();
+  std::vector<std::array<int32_t, 2>> fps_ranges;
   for (auto hal_format : hal_formats) {
     // Get the corresponding V4L2 format.
     uint32_t v4l2_format = StreamFormat::HalToV4L2PixelFormat(hal_format);
@@ -105,6 +123,42 @@
       // came from translating a bunch of V4L2 formats above.
       HAL_LOGE("Couldn't find V4L2 format for HAL format %d", hal_format);
       return -ENODEV;
+    } else if (unsupported_hal_formats.find(hal_format) !=
+               unsupported_hal_formats.end()) {
+      if (hal_format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+        if (qualified_formats.size() != 0) {
+          v4l2_format = qualified_formats[0];
+        } else if (unsupported_hal_formats.size() == 1) {
+          v4l2_format = StreamFormat::HalToV4L2PixelFormat(
+              HAL_PIXEL_FORMAT_YCbCr_420_888);
+        } else {
+          // No-op. If there are no qualified formats, and implementation
+          // defined is not the only unsupported format, then other unsupported
+          // formats will throw an error.
+        }
+        HAL_LOGW(
+            "Implementation-defined format is set to V4L2 pixel format 0x%x",
+            v4l2_format);
+      } else if (qualified_formats.size() == 0) {
+        HAL_LOGE(
+            "Camera does not support required format: 0x%x, and there are no "
+            "qualified"
+            "formats to transform from.",
+            hal_format);
+        return -ENODEV;
+      } else if (!arc::ImageProcessor::SupportsConversion(V4L2_PIX_FMT_YUV420,
+                                                          v4l2_format)) {
+        HAL_LOGE(
+            "The image processor does not support conversion to required "
+            "format: 0x%x",
+            hal_format);
+        return -ENODEV;
+      } else {
+        v4l2_format = qualified_formats[0];
+        HAL_LOGW(
+            "Hal format 0x%x will be converted from V4L2 pixel format 0x%x",
+            hal_format, v4l2_format);
+      }
     }
 
     // Get the available sizes for this format.
@@ -160,39 +214,25 @@
       if (size_max_frame_duration < min_max_frame_duration) {
         min_max_frame_duration = size_max_frame_duration;
       }
-      // We only care about the largest min frame duration
-      // (smallest max frame rate) for YUV sizes.
-      if (hal_format == HAL_PIXEL_FORMAT_YCbCr_420_888 &&
-          size_min_frame_duration > max_min_frame_duration_yuv) {
-        max_min_frame_duration_yuv = size_min_frame_duration;
+      // ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES will contain all
+      // the fps ranges for YUV_420_888 only since YUV_420_888 format is
+      // the default camera format by Android.
+      if (hal_format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
+        // Convert from frame durations measured in ns.
+        // Min, max fps supported by all YUV formats.
+        const int32_t min_fps = 1000000000 / size_max_frame_duration;
+        const int32_t max_fps = 1000000000 / size_min_frame_duration;
+        if (std::find(fps_ranges.begin(), fps_ranges.end(),
+                      std::array<int32_t, 2>{min_fps, max_fps}) ==
+            fps_ranges.end()) {
+          fps_ranges.push_back({min_fps, max_fps});
+        }
       }
     }
   }
 
-  // Convert from frame durations measured in ns.
-  // Min fps supported by all formats.
-  int32_t min_fps = 1000000000 / min_max_frame_duration;
-  if (min_fps > 15) {
-    HAL_LOGE("Minimum FPS %d is larger than HAL max allowable value of 15",
-             min_fps);
-    return -ENODEV;
-  }
-  // Max fps supported by all YUV formats.
-  int32_t max_yuv_fps = 1000000000 / max_min_frame_duration_yuv;
-  // ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES should be at minimum
-  // {mi, ma}, {ma, ma} where mi and ma are min and max frame rates for
-  // YUV_420_888. Min should be at most 15.
-  std::vector<std::array<int32_t, 2>> fps_ranges;
-  fps_ranges.push_back({{min_fps, max_yuv_fps}});
-
-  std::array<int32_t, 2> video_fps_range;
-  int32_t video_fps = 30;
-  if (video_fps >= max_yuv_fps) {
-    video_fps_range = {{max_yuv_fps, max_yuv_fps}};
-  } else {
-    video_fps_range = {{video_fps, video_fps}};
-  }
-  fps_ranges.push_back(video_fps_range);
+  // Sort fps ranges in descending order.
+  std::sort(fps_ranges.begin(), fps_ranges.end(), FpsRangesCompare);
 
   // Construct the metadata components.
   insertion_point = std::make_unique<Property<ArrayVector<int32_t, 4>>>(
@@ -208,10 +248,9 @@
   // TODO(b/31019725): This should probably not be a NoEffect control.
   insertion_point = NoEffectMenuControl<std::array<int32_t, 2>>(
       ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
-      ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
-      fps_ranges,
-      {{CAMERA3_TEMPLATE_VIDEO_RECORD, video_fps_range},
-       {OTHER_TEMPLATES, fps_ranges[0]}});
+      ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, fps_ranges,
+      {{CAMERA3_TEMPLATE_VIDEO_RECORD, fps_ranges.front()},
+       {OTHER_TEMPLATES, fps_ranges.back()}});
 
   return 0;
 }
diff --git a/modules/camera/3_4/format_metadata_factory.h b/modules/camera/3_4/format_metadata_factory.h
index 4cf5952..23c1777 100644
--- a/modules/camera/3_4/format_metadata_factory.h
+++ b/modules/camera/3_4/format_metadata_factory.h
@@ -17,6 +17,7 @@
 #ifndef V4L2_CAMERA_HAL_FORMAT_METADATA_FACTORY_H_
 #define V4L2_CAMERA_HAL_FORMAT_METADATA_FACTORY_H_
 
+#include <algorithm>
 #include <iterator>
 #include <memory>
 #include <set>
diff --git a/modules/camera/3_4/format_metadata_factory_test.cpp b/modules/camera/3_4/format_metadata_factory_test.cpp
index d37b09f..fe5d67f 100644
--- a/modules/camera/3_4/format_metadata_factory_test.cpp
+++ b/modules/camera/3_4/format_metadata_factory_test.cpp
@@ -46,10 +46,12 @@
 };
 
 TEST_F(FormatMetadataFactoryTest, GetFormatMetadata) {
-  std::set<uint32_t> formats{V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420};
+  std::set<uint32_t> formats{V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420,
+                             V4L2_PIX_FMT_YUYV};
   std::map<uint32_t, std::set<std::array<int32_t, 2>>> sizes{
       {V4L2_PIX_FMT_JPEG, {{{10, 20}}, {{30, 60}}, {{120, 240}}}},
-      {V4L2_PIX_FMT_YUV420, {{{1, 2}}, {{3, 6}}, {{12, 24}}}}};
+      {V4L2_PIX_FMT_YUV420, {{{1, 2}}, {{3, 6}}, {{12, 24}}}},
+      {V4L2_PIX_FMT_YUYV, {{{20, 40}}, {{80, 160}}, {{320, 640}}}}};
   // These need to be on the correct order of magnitude,
   // as there is a check for min fps > 15.
   std::map<uint32_t, std::map<std::array<int32_t, 2>, std::array<int64_t, 2>>>
@@ -60,19 +62,24 @@
                 {V4L2_PIX_FMT_YUV420,
                  {{{{1, 2}}, {{10000000000, 20000000000}}},
                   {{{3, 6}}, {{11000000000, 21000000000}}},
-                  {{{12, 24}}, {{10500000000, 19000000000}}}}}};
+                  {{{12, 24}}, {{10500000000, 19000000000}}}}},
+                {V4L2_PIX_FMT_YUYV,
+                 {{{{20, 40}}, {{11000000000, 22000000000}}},
+                  {{{80, 160}}, {{13000000000, 25000000000}}},
+                  {{{320, 640}}, {{10100000000, 19000000000}}}}}};
+  // The camera must report at least one qualified format.
+  std::vector<uint32_t> qualified_formats = {V4L2_PIX_FMT_YUYV};
 
   // Device must support IMPLEMENTATION_DEFINED (as well as JPEG & YUV).
-  // Just duplicate the values from another format.
-  uint32_t imp_defined_format = StreamFormat::HalToV4L2PixelFormat(
-      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
-  formats.insert(imp_defined_format);
-  sizes[imp_defined_format] = sizes[V4L2_PIX_FMT_YUV420];
-  durations[imp_defined_format] = durations[V4L2_PIX_FMT_YUV420];
+  // For USB cameras, we assume that this format will not be present, and it
+  // will default to a qualified format or one of the other required formats.
 
   EXPECT_CALL(*mock_device_, GetFormats(_))
       .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
 
+  EXPECT_CALL(*mock_device_, GetQualifiedFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(qualified_formats), Return(0)));
+
   for (auto format : formats) {
     std::set<std::array<int32_t, 2>> format_sizes = sizes[format];
     EXPECT_CALL(*mock_device_, GetFormatFrameSizes(format, _))
@@ -94,7 +101,7 @@
   for (auto& component : components) {
     android::CameraMetadata metadata;
     component->PopulateStaticFields(&metadata);
-    ASSERT_EQ(metadata.entryCount(), 1);
+    ASSERT_EQ(metadata.entryCount(), 1u);
     int32_t tag = component->StaticTags()[0];
     switch (tag) {
       case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS:  // Fall through.
@@ -119,39 +126,71 @@
   }
 }
 
-TEST_F(FormatMetadataFactoryTest, GetFormatMetadataMissingJpeg) {
-  uint32_t imp_defined_format = StreamFormat::HalToV4L2PixelFormat(
-      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
-  std::set<uint32_t> formats{V4L2_PIX_FMT_YUV420, imp_defined_format};
-  EXPECT_CALL(*mock_device_, GetFormats(_))
-      .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
-  PartialMetadataSet components;
-  ASSERT_EQ(AddFormatComponents(mock_device_,
-                                std::inserter(components, components.end())),
-            -ENODEV);
-}
+TEST_F(FormatMetadataFactoryTest, GetFormatMetadataMissingRequired) {
+  std::set<uint32_t> formats{V4L2_PIX_FMT_YUYV};
+  std::map<uint32_t, std::set<std::array<int32_t, 2>>> sizes{
+      {V4L2_PIX_FMT_YUYV, {{{640, 480}}, {{320, 240}}}}};
+  std::map<uint32_t, std::map<std::array<int32_t, 2>, std::array<int64_t, 2>>>
+      durations{{V4L2_PIX_FMT_YUYV,
+                 {{{{640, 480}}, {{100000000, 200000000}}},
+                  {{{320, 240}}, {{100000000, 200000000}}}}}};
 
-TEST_F(FormatMetadataFactoryTest, GetFormatMetadataMissingYuv) {
-  uint32_t imp_defined_format = StreamFormat::HalToV4L2PixelFormat(
-      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
-  std::set<uint32_t> formats{V4L2_PIX_FMT_JPEG, imp_defined_format};
   EXPECT_CALL(*mock_device_, GetFormats(_))
       .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
-  PartialMetadataSet components;
-  ASSERT_EQ(AddFormatComponents(mock_device_,
-                                std::inserter(components, components.end())),
-            -ENODEV);
-}
+  // If a qualified format is present, we expect that required fields are
+  // populated as if they are supported.
+  std::vector<uint32_t> qualified_formats = {V4L2_PIX_FMT_YUYV};
 
-TEST_F(FormatMetadataFactoryTest,
-       GetFormatMetadataMissingImplementationDefined) {
-  std::set<uint32_t> formats{V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420};
-  EXPECT_CALL(*mock_device_, GetFormats(_))
-      .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
+  EXPECT_CALL(*mock_device_, GetQualifiedFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(qualified_formats), Return(0)));
+
+  for (auto format : formats) {
+    std::set<std::array<int32_t, 2>> format_sizes = sizes[format];
+    EXPECT_CALL(*mock_device_, GetFormatFrameSizes(format, _))
+        .Times(AtLeast(1))
+        .WillRepeatedly(DoAll(SetArgPointee<1>(format_sizes), Return(0)));
+    for (auto size : format_sizes) {
+      EXPECT_CALL(*mock_device_, GetFormatFrameDurationRange(format, size, _))
+          .Times(AtLeast(1))
+          .WillRepeatedly(
+              DoAll(SetArgPointee<2>(durations[format][size]), Return(0)));
+    }
+  }
+
+  // Check that all required formats are present.
   PartialMetadataSet components;
   ASSERT_EQ(AddFormatComponents(mock_device_,
                                 std::inserter(components, components.end())),
-            -ENODEV);
+            0);
+
+  std::vector<std::array<int32_t, 2>> target_fps_ranges{{{5, 10}}, {{10, 10}}};
+  for (auto& component : components) {
+    android::CameraMetadata metadata;
+    component->PopulateStaticFields(&metadata);
+    ASSERT_EQ(metadata.entryCount(), 1u);
+    int32_t tag = component->StaticTags()[0];
+    switch (tag) {
+      case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS:  // Fall through.
+      case ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS:    // Fall through.
+      case ANDROID_SCALER_AVAILABLE_STALL_DURATIONS:        // Fall through.
+        // Two sizes per format, four elements per config.
+        // # formats + 3 for YUV420, JPEG, IMPLEMENTATION_DEFINED.
+        ExpectMetadataTagCount(metadata, tag, (formats.size() + 3) * 2 * 4);
+        break;
+      case ANDROID_SENSOR_INFO_MAX_FRAME_DURATION:
+        // The lowest max duration from above.
+        ExpectMetadataEq(metadata, tag, 200000000);
+        break;
+      case ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES:
+        // 2 ranges ({min, max} and {max, max}), each with a min and max.
+        ExpectMetadataTagCount(metadata, tag, 4);
+        ExpectMetadataEq(metadata, tag, target_fps_ranges);
+        break;
+      default:
+        FAIL() << "Unexpected component created.";
+        break;
+    }
+  }
 }
 
 }  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/control_test.cpp b/modules/camera/3_4/metadata/control_test.cpp
index f76376c..6284330 100644
--- a/modules/camera/3_4/metadata/control_test.cpp
+++ b/modules/camera/3_4/metadata/control_test.cpp
@@ -64,15 +64,15 @@
 
   virtual void ExpectTags() {
     if (use_options_ && report_options_) {
-      ASSERT_EQ(control_->StaticTags().size(), 1);
+      ASSERT_EQ(control_->StaticTags().size(), 1u);
       EXPECT_EQ(control_->StaticTags()[0], options_tag_);
     } else {
       EXPECT_TRUE(control_->StaticTags().empty());
     }
     // Controls use the same delgate, and thus tag, for getting and setting.
-    ASSERT_EQ(control_->ControlTags().size(), 1);
+    ASSERT_EQ(control_->ControlTags().size(), 1u);
     EXPECT_EQ(control_->ControlTags()[0], delegate_tag_);
-    ASSERT_EQ(control_->DynamicTags().size(), 1);
+    ASSERT_EQ(control_->DynamicTags().size(), 1u);
     EXPECT_EQ(control_->DynamicTags()[0], delegate_tag_);
   }
 
@@ -81,10 +81,10 @@
     android::CameraMetadata metadata;
     ASSERT_EQ(control_->PopulateStaticFields(&metadata), 0);
     if (use_options_ && report_options_) {
-      EXPECT_EQ(metadata.entryCount(), 1);
+      EXPECT_EQ(metadata.entryCount(), 1u);
       ExpectMetadataEq(metadata, options_tag_, options);
     } else {
-      EXPECT_EQ(metadata.entryCount(), 0);
+      EXPECT_EQ(metadata.entryCount(), 0u);
       // Shouldn't be expecting any options.
       EXPECT_TRUE(options.empty());
     }
@@ -93,7 +93,7 @@
   virtual void ExpectValue(uint8_t value) {
     android::CameraMetadata metadata;
     ASSERT_EQ(control_->PopulateDynamicFields(&metadata), 0);
-    EXPECT_EQ(metadata.entryCount(), 1);
+    EXPECT_EQ(metadata.entryCount(), 1u);
     ExpectMetadataEq(metadata, delegate_tag_, value);
   }
 
diff --git a/modules/camera/3_4/metadata/partial_metadata_factory_test.cpp b/modules/camera/3_4/metadata/partial_metadata_factory_test.cpp
index 9ca3a38..3537ed2 100644
--- a/modules/camera/3_4/metadata/partial_metadata_factory_test.cpp
+++ b/modules/camera/3_4/metadata/partial_metadata_factory_test.cpp
@@ -44,11 +44,11 @@
   }
 
   virtual void ExpectControlTags() {
-    ASSERT_EQ(control_->StaticTags().size(), 1);
+    ASSERT_EQ(control_->StaticTags().size(), 1u);
     EXPECT_EQ(control_->StaticTags()[0], options_tag_);
-    ASSERT_EQ(control_->ControlTags().size(), 1);
+    ASSERT_EQ(control_->ControlTags().size(), 1u);
     EXPECT_EQ(control_->ControlTags()[0], delegate_tag_);
-    ASSERT_EQ(control_->DynamicTags().size(), 1);
+    ASSERT_EQ(control_->DynamicTags().size(), 1u);
     EXPECT_EQ(control_->DynamicTags()[0], delegate_tag_);
   }
 
@@ -56,14 +56,14 @@
     // Options should be available.
     android::CameraMetadata metadata;
     ASSERT_EQ(control_->PopulateStaticFields(&metadata), 0);
-    EXPECT_EQ(metadata.entryCount(), 1);
+    EXPECT_EQ(metadata.entryCount(), 1u);
     ExpectMetadataEq(metadata, options_tag_, options);
   }
 
   virtual void ExpectControlValue(uint8_t value) {
     android::CameraMetadata metadata;
     ASSERT_EQ(control_->PopulateDynamicFields(&metadata), 0);
-    EXPECT_EQ(metadata.entryCount(), 1);
+    EXPECT_EQ(metadata.entryCount(), 1u);
     ExpectMetadataEq(metadata, delegate_tag_, value);
   }
 
@@ -84,14 +84,14 @@
   uint8_t value = 13;
   std::unique_ptr<State<uint8_t>> state = FixedState(delegate_tag_, value);
 
-  ASSERT_EQ(state->StaticTags().size(), 0);
-  ASSERT_EQ(state->ControlTags().size(), 0);
-  ASSERT_EQ(state->DynamicTags().size(), 1);
+  ASSERT_EQ(state->StaticTags().size(), 0u);
+  ASSERT_EQ(state->ControlTags().size(), 0u);
+  ASSERT_EQ(state->DynamicTags().size(), 1u);
   EXPECT_EQ(state->DynamicTags()[0], delegate_tag_);
 
   android::CameraMetadata metadata;
   ASSERT_EQ(state->PopulateDynamicFields(&metadata), 0);
-  EXPECT_EQ(metadata.entryCount(), 1);
+  EXPECT_EQ(metadata.entryCount(), 1u);
   ExpectMetadataEq(metadata, delegate_tag_, value);
 }
 
diff --git a/modules/camera/3_4/metadata/property_test.cpp b/modules/camera/3_4/metadata/property_test.cpp
index 8e947ea..80f8eb8 100644
--- a/modules/camera/3_4/metadata/property_test.cpp
+++ b/modules/camera/3_4/metadata/property_test.cpp
@@ -49,9 +49,9 @@
   Property<int32_t> property(int_tag_, 1);
 
   // Should have only the single tag it was constructed with.
-  EXPECT_EQ(property.ControlTags().size(), 0);
-  EXPECT_EQ(property.DynamicTags().size(), 0);
-  ASSERT_EQ(property.StaticTags().size(), 1);
+  EXPECT_EQ(property.ControlTags().size(), 0u);
+  EXPECT_EQ(property.DynamicTags().size(), 0u);
+  ASSERT_EQ(property.StaticTags().size(), 1u);
   // The macro doesn't like the int_tag_ variable being passed in directly.
   int32_t expected_tag = int_tag_;
   EXPECT_EQ(property.StaticTags()[0], expected_tag);
@@ -68,7 +68,7 @@
 
   // Check the results.
   // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
+  EXPECT_EQ(metadata.entryCount(), 1u);
   // Should have added the right entry.
   ExpectMetadataEq(metadata, int_tag_, data);
 }
@@ -86,7 +86,7 @@
 
   // Check the results.
   // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
+  EXPECT_EQ(metadata.entryCount(), 1u);
   // Should have added the right entry.
   ExpectMetadataEq(metadata, float_tag_, data);
 }
@@ -102,7 +102,7 @@
 
   // Check the results.
   // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
+  EXPECT_EQ(metadata.entryCount(), 1u);
   // Should have added the right entry.
   ExpectMetadataEq(metadata, float_tag_, data);
 }
@@ -120,7 +120,7 @@
 
   // Check the results.
   // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
+  EXPECT_EQ(metadata.entryCount(), 1u);
   // Should have added the right entry.
   ExpectMetadataEq(metadata, byte_tag_, data);
 }
diff --git a/modules/camera/3_4/metadata/state_test.cpp b/modules/camera/3_4/metadata/state_test.cpp
index 5c308bc..ecc1d15 100644
--- a/modules/camera/3_4/metadata/state_test.cpp
+++ b/modules/camera/3_4/metadata/state_test.cpp
@@ -59,7 +59,7 @@
   PrepareState();
   EXPECT_TRUE(state_->StaticTags().empty());
   EXPECT_TRUE(state_->ControlTags().empty());
-  ASSERT_EQ(state_->DynamicTags().size(), 1);
+  ASSERT_EQ(state_->DynamicTags().size(), 1u);
   EXPECT_EQ(state_->DynamicTags()[0], tag_);
 }
 
@@ -79,7 +79,7 @@
 
   android::CameraMetadata metadata;
   ASSERT_EQ(state_->PopulateDynamicFields(&metadata), 0);
-  EXPECT_EQ(metadata.entryCount(), 1);
+  EXPECT_EQ(metadata.entryCount(), 1u);
   ExpectMetadataEq(metadata, tag_, expected);
 }
 
diff --git a/modules/camera/3_4/metadata/test_common.h b/modules/camera/3_4/metadata/test_common.h
index 489990f..42e44f0 100644
--- a/modules/camera/3_4/metadata/test_common.h
+++ b/modules/camera/3_4/metadata/test_common.h
@@ -80,9 +80,9 @@
 
 // Vector of arrays.
 template <typename T, size_t N>
-static int ExpectMetadataEq(const android::CameraMetadata& metadata,
-                            int32_t tag,
-                            const std::vector<std::array<T, N>>& expected) {
+static void ExpectMetadataEq(const android::CameraMetadata& metadata,
+                             int32_t tag,
+                             const std::vector<std::array<T, N>>& expected) {
   // Convert to array vector so we know all the elements are contiguous.
   ArrayVector<T, N> array_vector;
   for (const auto& array : expected) {
diff --git a/modules/camera/3_4/stream_format.cpp b/modules/camera/3_4/stream_format.cpp
index 70900ab..5f35e42 100644
--- a/modules/camera/3_4/stream_format.cpp
+++ b/modules/camera/3_4/stream_format.cpp
@@ -20,10 +20,21 @@
 
 #include <system/graphics.h>
 
+#include "arc/image_processor.h"
 #include "common.h"
 
 namespace v4l2_camera_hal {
 
+using arc::SupportedFormat;
+using arc::SupportedFormats;
+
+static const std::vector<uint32_t> GetSupportedFourCCs() {
+  // The preference of supported fourccs in the list is from high to low.
+  static const std::vector<uint32_t> kSupportedFourCCs = {V4L2_PIX_FMT_YUYV,
+                                                          V4L2_PIX_FMT_MJPEG};
+  return kSupportedFourCCs;
+}
+
 StreamFormat::StreamFormat(int format, uint32_t width, uint32_t height)
     // TODO(b/30000211): multiplanar support.
     : type_(V4L2_BUF_TYPE_VIDEO_CAPTURE),
@@ -42,6 +53,14 @@
       bytes_per_line_(format.fmt.pix.bytesperline),
       min_buffer_size_(format.fmt.pix.sizeimage) {}
 
+StreamFormat::StreamFormat(const arc::SupportedFormat& format)
+    : type_(V4L2_BUF_TYPE_VIDEO_CAPTURE),
+      v4l2_pixel_format_(format.fourcc),
+      width_(format.width),
+      height_(format.height),
+      bytes_per_line_(0),
+      min_buffer_size_(0) {}
+
 void StreamFormat::FillFormatRequest(v4l2_format* format) const {
   memset(format, 0, sizeof(*format));
   format->type = type_;
@@ -57,7 +76,7 @@
     case V4L2_PIX_FMT_JPEG:
       return kFormatCategoryStalling;
     case V4L2_PIX_FMT_YUV420:  // Fall through.
-    case V4L2_PIX_FMT_RGB24:
+    case V4L2_PIX_FMT_BGR32:
       return kFormatCategoryNonStalling;
     default:
       // Note: currently no supported RAW formats.
@@ -79,47 +98,127 @@
 
 int StreamFormat::V4L2ToHalPixelFormat(uint32_t v4l2_pixel_format) {
   // Translate V4L2 format to HAL format.
-  int hal_pixel_format = -1;
   switch (v4l2_pixel_format) {
+    case V4L2_PIX_FMT_BGR32:
+      return HAL_PIXEL_FORMAT_RGBA_8888;
     case V4L2_PIX_FMT_JPEG:
-      hal_pixel_format = HAL_PIXEL_FORMAT_BLOB;
-      break;
+      return HAL_PIXEL_FORMAT_BLOB;
+    case V4L2_PIX_FMT_NV21:
+      return HAL_PIXEL_FORMAT_YCrCb_420_SP;
     case V4L2_PIX_FMT_YUV420:
-      hal_pixel_format = HAL_PIXEL_FORMAT_YCbCr_420_888;
-      break;
-    case V4L2_PIX_FMT_RGB24:
-      hal_pixel_format = HAL_PIXEL_FORMAT_RGBA_8888;
-      break;
+      return HAL_PIXEL_FORMAT_YCbCr_420_888;
+    case V4L2_PIX_FMT_YUYV:
+      return HAL_PIXEL_FORMAT_YCbCr_422_I;
+    case V4L2_PIX_FMT_YVU420:
+      return HAL_PIXEL_FORMAT_YV12;
     default:
       // Unrecognized format.
       HAL_LOGV("Unrecognized v4l2 pixel format %u", v4l2_pixel_format);
       break;
   }
-  return hal_pixel_format;
+  return -1;
 }
 
 uint32_t StreamFormat::HalToV4L2PixelFormat(int hal_pixel_format) {
-  // Translate HAL format to V4L2 format.
-  uint32_t v4l2_pixel_format = 0;
   switch (hal_pixel_format) {
-    case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:  // fall-through.
-    case HAL_PIXEL_FORMAT_RGBA_8888:
-      // Should be RGB32, but RPi doesn't support that.
-      // For now we accept that the colors will be off.
-      v4l2_pixel_format = V4L2_PIX_FMT_RGB24;
-      break;
-    case HAL_PIXEL_FORMAT_YCbCr_420_888:
-      v4l2_pixel_format = V4L2_PIX_FMT_YUV420;
-      break;
     case HAL_PIXEL_FORMAT_BLOB:
-      v4l2_pixel_format = V4L2_PIX_FMT_JPEG;
-      break;
+      return V4L2_PIX_FMT_JPEG;
+    case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:  // Fall-through
+    case HAL_PIXEL_FORMAT_RGBA_8888:
+      return V4L2_PIX_FMT_BGR32;
+    case HAL_PIXEL_FORMAT_YCbCr_420_888:
+      // This is a flexible YUV format that depends on platform. Different
+      // platform may have different format. It can be YVU420 or NV12. Now we
+      // return YVU420 first.
+      // TODO(): call drm_drv.get_fourcc() to get correct format.
+      return V4L2_PIX_FMT_YUV420;
+    case HAL_PIXEL_FORMAT_YCbCr_422_I:
+      return V4L2_PIX_FMT_YUYV;
+    case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+      return V4L2_PIX_FMT_NV21;
+    case HAL_PIXEL_FORMAT_YV12:
+      return V4L2_PIX_FMT_YVU420;
     default:
-      // Unrecognized format.
-      HAL_LOGV("Unrecognized HAL pixel format %d", hal_pixel_format);
+      HAL_LOGV("Pixel format 0x%x is unsupported.", hal_pixel_format);
       break;
   }
-  return v4l2_pixel_format;
+  return -1;
+}
+
+// Copy the qualified format into out_format and return true if there is a
+// proper and fitting format in the given format lists.
+bool StreamFormat::FindBestFitFormat(const SupportedFormats& supported_formats,
+                                     const SupportedFormats& qualified_formats,
+                                     uint32_t fourcc, uint32_t width,
+                                     uint32_t height,
+                                     SupportedFormat* out_format) {
+  // Match exact format and resolution if possible.
+  for (const auto& format : supported_formats) {
+    if (format.fourcc == fourcc && format.width == width &&
+        format.height == height) {
+      if (out_format != NULL) {
+        *out_format = format;
+      }
+      return true;
+    }
+  }
+  // All conversions will be done through CachedFrame for now, which will
+  // immediately convert the qualified format into YU12 (YUV420). We check
+  // here that the conversion between YU12 and |fourcc| is supported.
+  if (!arc::ImageProcessor::SupportsConversion(V4L2_PIX_FMT_YUV420, fourcc)) {
+    HAL_LOGE("Conversion between YU12 and 0x%x not supported.", fourcc);
+    return false;
+  }
+
+  // Choose the qualified format with a matching resolution.
+  for (const auto& format : qualified_formats) {
+    if (format.width == width && format.height == height) {
+      if (out_format != NULL) {
+        *out_format = format;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+// Copy corresponding format into out_format and return true by matching
+// resolution |width|x|height| in |formats|.
+bool StreamFormat::FindFormatByResolution(const SupportedFormats& formats,
+                                          uint32_t width, uint32_t height,
+                                          SupportedFormat* out_format) {
+  for (const auto& format : formats) {
+    if (format.width == width && format.height == height) {
+      if (out_format != NULL) {
+        *out_format = format;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+SupportedFormats StreamFormat::GetQualifiedFormats(
+    const SupportedFormats& supported_formats) {
+  // The preference of supported fourccs in the list is from high to low.
+  const std::vector<uint32_t> supported_fourccs = GetSupportedFourCCs();
+  SupportedFormats qualified_formats;
+  for (const auto& supported_fourcc : supported_fourccs) {
+    for (const auto& supported_format : supported_formats) {
+      if (supported_format.fourcc != supported_fourcc) {
+        continue;
+      }
+
+      // Skip if |qualified_formats| already has the same resolution with a more
+      // preferred fourcc.
+      if (FindFormatByResolution(qualified_formats, supported_format.width,
+                                 supported_format.height, NULL)) {
+        continue;
+      }
+      qualified_formats.push_back(supported_format);
+    }
+  }
+  return qualified_formats;
 }
 
 }  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/stream_format.h b/modules/camera/3_4/stream_format.h
index 66c5965..720e380 100644
--- a/modules/camera/3_4/stream_format.h
+++ b/modules/camera/3_4/stream_format.h
@@ -21,6 +21,7 @@
 
 #include <linux/videodev2.h>
 
+#include "arc/common_types.h"
 #include "common.h"
 
 namespace v4l2_camera_hal {
@@ -36,6 +37,7 @@
  public:
   StreamFormat(int format, uint32_t width, uint32_t height);
   StreamFormat(const v4l2_format& format);
+  StreamFormat(const arc::SupportedFormat& format);
   virtual ~StreamFormat() = default;
   // Only uint32_t members, use default generated copy and assign.
 
@@ -44,6 +46,9 @@
 
   // Accessors.
   inline uint32_t type() const { return type_; };
+  inline uint32_t width() const { return width_; };
+  inline uint32_t height() const { return height_; };
+  inline uint32_t v4l2_pixel_format() const { return v4l2_pixel_format_; }
   inline uint32_t bytes_per_line() const { return bytes_per_line_; };
 
   bool operator==(const StreamFormat& other) const;
@@ -55,6 +60,18 @@
   // Returns -1 for unrecognized.
   static int V4L2ToHalPixelFormat(uint32_t v4l2_pixel_format);
 
+  // ARC++ SupportedFormat Helpers
+  static bool FindBestFitFormat(const arc::SupportedFormats& supported_formats,
+                                const arc::SupportedFormats& qualified_formats,
+                                uint32_t fourcc, uint32_t width,
+                                uint32_t height,
+                                arc::SupportedFormat* out_format);
+  static bool FindFormatByResolution(const arc::SupportedFormats& formats,
+                                     uint32_t width, uint32_t height,
+                                     arc::SupportedFormat* out_format);
+  static arc::SupportedFormats GetQualifiedFormats(
+      const arc::SupportedFormats& supported_formats);
+
  private:
   uint32_t type_;
   uint32_t v4l2_pixel_format_;
diff --git a/modules/camera/3_4/v4l2_camera.cpp b/modules/camera/3_4/v4l2_camera.cpp
index 22406c9..98b8062 100644
--- a/modules/camera/3_4/v4l2_camera.cpp
+++ b/modules/camera/3_4/v4l2_camera.cpp
@@ -123,16 +123,7 @@
 
 int V4L2Camera::flushBuffers() {
   HAL_LOG_ENTER();
-
-  int res = device_->StreamOff();
-
-  // This is not strictly necessary, but prevents a buildup of aborted
-  // requests in the in_flight_ map. These should be cleared
-  // whenever the stream is turned off.
-  std::lock_guard<std::mutex> guard(in_flight_lock_);
-  in_flight_.clear();
-
-  return res;
+  return device_->StreamOff();
 }
 
 int V4L2Camera::initStaticInfo(android::CameraMetadata* out) {
@@ -262,55 +253,41 @@
   }
 
   // Actually enqueue the buffer for capture.
-  {
-    std::lock_guard<std::mutex> guard(in_flight_lock_);
-
-    uint32_t index;
-    res = device_->EnqueueBuffer(&request->output_buffers[0], &index);
-    if (res) {
-      HAL_LOGE("Device failed to enqueue buffer.");
-      completeRequest(request, res);
-      return true;
-    }
-
-    // Make sure the stream is on (no effect if already on).
-    res = device_->StreamOn();
-    if (res) {
-      HAL_LOGE("Device failed to turn on stream.");
-      // Don't really want to send an error for only the request here,
-      // since this is a full device error.
-      // TODO: Should trigger full flush.
-      return true;
-    }
-
-    // Note: the request should be dequeued/flushed from the device
-    // before removal from in_flight_.
-    in_flight_.emplace(index, request);
-    buffers_in_flight_.notify_one();
+  res = device_->EnqueueRequest(request);
+  if (res) {
+    HAL_LOGE("Device failed to enqueue buffer.");
+    completeRequest(request, res);
+    return true;
   }
 
+  // Make sure the stream is on (no effect if already on).
+  res = device_->StreamOn();
+  if (res) {
+    HAL_LOGE("Device failed to turn on stream.");
+    // Don't really want to send an error for only the request here,
+    // since this is a full device error.
+    // TODO: Should trigger full flush.
+    return true;
+  }
+
+  std::unique_lock<std::mutex> lock(in_flight_lock_);
+  in_flight_buffer_count_++;
+  buffers_in_flight_.notify_one();
   return true;
 }
 
 bool V4L2Camera::dequeueRequestBuffers() {
   // Dequeue a buffer.
-  uint32_t result_index;
+  std::shared_ptr<default_camera_hal::CaptureRequest> request;
   int res;
 
   {
-    std::lock_guard<std::mutex> guard(in_flight_lock_);
-    res = device_->DequeueBuffer(&result_index);
+    std::unique_lock<std::mutex> lock(in_flight_lock_);
+    res = device_->DequeueRequest(&request);
     if (!res) {
-      // Find the associated request and complete it.
-      auto index_request = in_flight_.find(result_index);
-      if (index_request != in_flight_.end()) {
-        completeRequest(index_request->second, 0);
-        in_flight_.erase(index_request);
-      } else {
-        HAL_LOGW(
-            "Dequeued non in-flight buffer index %d. "
-            "This buffer may have been flushed from the HAL but not the device.",
-            index_request->first);
+      if (request) {
+        completeRequest(request, res);
+        in_flight_buffer_count_--;
       }
       return true;
     }
@@ -320,7 +297,7 @@
     // EAGAIN just means nothing to dequeue right now.
     // Wait until something is available before looping again.
     std::unique_lock<std::mutex> lock(in_flight_lock_);
-    while (in_flight_.empty()) {
+    while (in_flight_buffer_count_ == 0) {
       buffers_in_flight_.wait(lock);
     }
   } else {
@@ -350,10 +327,11 @@
 
   std::lock_guard<std::mutex> guard(in_flight_lock_);
   // The framework should be enforcing this, but doesn't hurt to be safe.
-  if (!in_flight_.empty()) {
+  if (device_->GetInFlightBufferCount() != 0) {
     HAL_LOGE("Can't set device format while frames are in flight.");
     return -EINVAL;
   }
+  in_flight_buffer_count_ = 0;
 
   // stream_config should have been validated; assume at least 1 stream.
   camera3_stream_t* stream = stream_config->streams[0];
diff --git a/modules/camera/3_4/v4l2_camera.h b/modules/camera/3_4/v4l2_camera.h
index 1db8d40..fc2adb3 100644
--- a/modules/camera/3_4/v4l2_camera.h
+++ b/modules/camera/3_4/v4l2_camera.h
@@ -99,9 +99,7 @@
   std::queue<std::shared_ptr<default_camera_hal::CaptureRequest>>
       request_queue_;
   std::mutex in_flight_lock_;
-  // Maps buffer index : request.
-  std::map<uint32_t, std::shared_ptr<default_camera_hal::CaptureRequest>>
-      in_flight_;
+  uint32_t in_flight_buffer_count_;
   // Threads require holding an Android strong pointer.
   android::sp<android::Thread> buffer_enqueuer_;
   android::sp<android::Thread> buffer_dequeuer_;
diff --git a/modules/camera/3_4/v4l2_gralloc.cpp b/modules/camera/3_4/v4l2_gralloc.cpp
deleted file mode 100644
index 2fcef35..0000000
--- a/modules/camera/3_4/v4l2_gralloc.cpp
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "v4l2_gralloc.h"
-
-#include <linux/videodev2.h>
-
-#include <cstdlib>
-
-#include <hardware/camera3.h>
-#include <hardware/gralloc.h>
-#include <system/graphics.h>
-
-#include "common.h"
-#include "stream_format.h"
-
-namespace v4l2_camera_hal {
-
-// Copy |height| lines from |src| to |dest|,
-// where |src| and |dest| may have different line lengths.
-void copyWithPadding(uint8_t* dest,
-                     const uint8_t* src,
-                     size_t dest_stride,
-                     size_t src_stride,
-                     size_t height) {
-  size_t copy_stride = dest_stride;
-  if (copy_stride > src_stride) {
-    // Adding padding, not reducing. 0 out the extra memory.
-    memset(dest, 0, src_stride * height);
-    copy_stride = src_stride;
-  }
-  uint8_t* dest_line_start = dest;
-  const uint8_t* src_line_start = src;
-  for (size_t row = 0; row < height;
-       ++row, dest_line_start += dest_stride, src_line_start += src_stride) {
-    memcpy(dest_line_start, src_line_start, copy_stride);
-  }
-}
-
-V4L2Gralloc* V4L2Gralloc::NewV4L2Gralloc() {
-  // Initialize and check the gralloc module.
-  const hw_module_t* module = nullptr;
-  int res = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
-  if (res || !module) {
-    HAL_LOGE("Couldn't get gralloc module.");
-    return nullptr;
-  }
-  const gralloc_module_t* gralloc =
-      reinterpret_cast<const gralloc_module_t*>(module);
-
-  // This class only supports Gralloc v0, not Gralloc V1.
-  if (gralloc->common.module_api_version > GRALLOC_MODULE_API_VERSION_0_3) {
-    HAL_LOGE(
-        "Invalid gralloc version %x. Only 0.3 (%x) "
-        "and below are supported by this HAL.",
-        gralloc->common.module_api_version,
-        GRALLOC_MODULE_API_VERSION_0_3);
-    return nullptr;
-  }
-
-  return new V4L2Gralloc(gralloc);
-}
-
-// Private. As checked by above factory, module will be non-null
-// and a supported version.
-V4L2Gralloc::V4L2Gralloc(const gralloc_module_t* module) : mModule(module) {}
-
-V4L2Gralloc::~V4L2Gralloc() {
-  // Unlock buffers that are still locked.
-  unlockAllBuffers();
-}
-
-int V4L2Gralloc::lock(const camera3_stream_buffer_t* camera_buffer,
-                      uint32_t bytes_per_line,
-                      v4l2_buffer* device_buffer) {
-  // Lock the camera buffer (varies depending on if the buffer is YUV or not).
-  std::unique_ptr<BufferData> buffer_data(
-      new BufferData{camera_buffer, nullptr, bytes_per_line});
-  buffer_handle_t buffer = *camera_buffer->buffer;
-  void* data;
-  camera3_stream_t* stream = camera_buffer->stream;
-  int ret = 0;
-  switch (StreamFormat::HalToV4L2PixelFormat(stream->format)) {
-    // TODO(b/30119452): support more YCbCr formats.
-    case V4L2_PIX_FMT_YUV420:
-      android_ycbcr yuv_data;
-      ret = mModule->lock_ycbcr(mModule,
-                                buffer,
-                                stream->usage,
-                                0,
-                                0,
-                                stream->width,
-                                stream->height,
-                                &yuv_data);
-      if (ret) {
-        HAL_LOGE("Failed to lock ycbcr buffer: %d", ret);
-        return ret;
-      }
-
-      // Check if gralloc format matches v4l2 format
-      // (same padding, not interleaved, contiguous).
-      if (yuv_data.ystride == bytes_per_line &&
-          yuv_data.cstride == bytes_per_line / 2 && yuv_data.chroma_step == 1 &&
-          (reinterpret_cast<uint8_t*>(yuv_data.cb) ==
-           reinterpret_cast<uint8_t*>(yuv_data.y) +
-               (stream->height * yuv_data.ystride)) &&
-          (reinterpret_cast<uint8_t*>(yuv_data.cr) ==
-           reinterpret_cast<uint8_t*>(yuv_data.cb) +
-               (stream->height / 2 * yuv_data.cstride))) {
-        // If so, great, point to the beginning.
-        data = yuv_data.y;
-      } else {
-        // If not, allocate a contiguous buffer of appropriate size
-        // (to be transformed back upon unlock).
-        data = new uint8_t[device_buffer->length];
-        // Make a dynamically-allocated copy of yuv_data,
-        // since it will be needed at transform time.
-        buffer_data->transform_dest.reset(new android_ycbcr(yuv_data));
-      }
-      break;
-    case V4L2_PIX_FMT_JPEG:
-      // Jpeg buffers are just contiguous blobs; lock length * 1.
-      ret = mModule->lock(mModule,
-                          buffer,
-                          stream->usage,
-                          0,
-                          0,
-                          device_buffer->length,
-                          1,
-                          &data);
-      if (ret) {
-        HAL_LOGE("Failed to lock jpeg buffer: %d", ret);
-        return ret;
-      }
-      break;
-    case V4L2_PIX_FMT_RGB24:  // Fall-through.
-    case V4L2_PIX_FMT_BGR32:  // Fall-through.
-    case V4L2_PIX_FMT_RGB32:
-      // RGB formats have nice agreed upon representation. Unless using android
-      // flex formats.
-      ret = mModule->lock(mModule,
-                          buffer,
-                          stream->usage,
-                          0,
-                          0,
-                          stream->width,
-                          stream->height,
-                          &data);
-      if (ret) {
-        HAL_LOGE("Failed to lock RGB buffer: %d", ret);
-        return ret;
-      }
-      break;
-    default:
-      return -EINVAL;
-  }
-
-  if (!data) {
-    ALOGE("Gralloc lock returned null ptr");
-    return -ENODEV;
-  }
-
-  // Set up the device buffer.
-  static_assert(sizeof(unsigned long) >= sizeof(void*),
-                "void* must be able to fit in the v4l2_buffer m.userptr "
-                "field (unsigned long) for this code to work");
-  device_buffer->m.userptr = reinterpret_cast<unsigned long>(data);
-
-  // Note the mapping of data:buffer info for when unlock is called.
-  mBufferMap.emplace(data, buffer_data.release());
-
-  return 0;
-}
-
-int V4L2Gralloc::unlock(const v4l2_buffer* device_buffer) {
-  // TODO(b/30000211): support multi-planar data (video_capture_mplane).
-  if (device_buffer->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
-    return -EINVAL;
-  }
-
-  void* data = reinterpret_cast<void*>(device_buffer->m.userptr);
-
-  // Find and pop the matching entry in the map.
-  auto map_entry = mBufferMap.find(data);
-  if (map_entry == mBufferMap.end()) {
-    HAL_LOGE("No matching buffer for data at %p", data);
-    return -EINVAL;
-  }
-  std::unique_ptr<const BufferData> buffer_data(map_entry->second);
-  mBufferMap.erase(map_entry);
-
-  const camera3_stream_buffer_t* camera_buffer = buffer_data->camera_buffer;
-  const buffer_handle_t buffer = *camera_buffer->buffer;
-
-  if (StreamFormat::HalToV4L2PixelFormat(camera_buffer->stream->format) == V4L2_PIX_FMT_RGB24) {
-    // Convert RGB24 to RGB32.
-    size_t rgb_size = camera_buffer->stream->width * camera_buffer->stream->height;
-    uint8_t* tail_rgb24 = (uint8_t*)data + 3 * rgb_size - 1;
-    uint8_t* tail_rgb32 = (uint8_t*)data + 4 * rgb_size - 1;
-    for (int i = 0; i < rgb_size; i++) {
-      *(tail_rgb32--) = 0xff;
-      *(tail_rgb32--) = *(tail_rgb24--);
-      *(tail_rgb32--) = *(tail_rgb24--);
-      *(tail_rgb32--) = *(tail_rgb24--);
-    }
-  }
-
-  // Check for transform.
-  if (buffer_data->transform_dest) {
-    HAL_LOGV("Transforming V4L2 YUV to gralloc YUV.");
-    // In this case data was allocated by this class, put it in a unique_ptr
-    // to ensure it gets cleaned up no matter which way this function exits.
-    std::unique_ptr<uint8_t[]> data_cleanup(reinterpret_cast<uint8_t*>(data));
-
-    uint32_t bytes_per_line = buffer_data->v4l2_bytes_per_line;
-    android_ycbcr* yuv_data = buffer_data->transform_dest.get();
-
-    // Should only occur in error situations.
-    if (device_buffer->bytesused == 0) {
-      return -EINVAL;
-    }
-
-    // Transform V4L2 to Gralloc, copying each plane to the correct place,
-    // adjusting padding, and interleaving if necessary.
-    uint32_t height = camera_buffer->stream->height;
-    // Y data first.
-    size_t y_len = bytes_per_line * height;
-    if (yuv_data->ystride == bytes_per_line) {
-      // Data should match exactly.
-      memcpy(yuv_data->y, data, y_len);
-    } else {
-      HAL_LOGV("Changing padding on Y plane from %u to %u.",
-               bytes_per_line,
-               yuv_data->ystride);
-      // Wrong padding from V4L2.
-      copyWithPadding(reinterpret_cast<uint8_t*>(yuv_data->y),
-                      reinterpret_cast<uint8_t*>(data),
-                      yuv_data->ystride,
-                      bytes_per_line,
-                      height);
-    }
-    // C data.
-    // TODO(b/30119452): These calculations assume YCbCr_420_888.
-    size_t c_len = y_len / 4;
-    uint32_t c_bytes_per_line = bytes_per_line / 2;
-    // V4L2 is packed, meaning the data is stored as contiguous {y, cb, cr}.
-    uint8_t* cb_device = reinterpret_cast<uint8_t*>(data) + y_len;
-    uint8_t* cr_device = cb_device + c_len;
-    size_t step = yuv_data->chroma_step;
-    if (step == 1) {
-      // Still planar.
-      if (yuv_data->cstride == c_bytes_per_line) {
-        // Data should match exactly.
-        memcpy(yuv_data->cb, cb_device, c_len);
-        memcpy(yuv_data->cr, cr_device, c_len);
-      } else {
-        HAL_LOGV("Changing padding on C plane from %u to %u.",
-                 c_bytes_per_line,
-                 yuv_data->cstride);
-        // Wrong padding from V4L2.
-        copyWithPadding(reinterpret_cast<uint8_t*>(yuv_data->cb),
-                        cb_device,
-                        yuv_data->cstride,
-                        c_bytes_per_line,
-                        height / 2);
-        copyWithPadding(reinterpret_cast<uint8_t*>(yuv_data->cr),
-                        cr_device,
-                        yuv_data->cstride,
-                        c_bytes_per_line,
-                        height / 2);
-      }
-    } else {
-      // Desire semiplanar (cb and cr interleaved).
-      HAL_LOGV("Interleaving cb and cr. Padding going from %u to %u.",
-               c_bytes_per_line,
-               yuv_data->cstride);
-      uint32_t c_height = height / 2;
-      uint32_t c_width = camera_buffer->stream->width / 2;
-      // Zero out destination
-      uint8_t* cb_gralloc = reinterpret_cast<uint8_t*>(yuv_data->cb);
-      uint8_t* cr_gralloc = reinterpret_cast<uint8_t*>(yuv_data->cr);
-      memset(cb_gralloc, 0, c_width * c_height * step);
-
-      // Interleaving means we need to copy the cb and cr bytes one by one.
-      for (size_t line = 0; line < c_height; ++line,
-                  cb_gralloc += yuv_data->cstride,
-                  cr_gralloc += yuv_data->cstride,
-                  cb_device += c_bytes_per_line,
-                  cr_device += c_bytes_per_line) {
-        for (size_t i = 0; i < c_width; ++i) {
-          *(cb_gralloc + (i * step)) = *(cb_device + i);
-          *(cr_gralloc + (i * step)) = *(cr_device + i);
-        }
-      }
-    }
-  }
-
-  // Unlock.
-  int res = mModule->unlock(mModule, buffer);
-  if (res) {
-    HAL_LOGE("Failed to unlock buffer at %p", buffer);
-    return -ENODEV;
-  }
-
-  return 0;
-}
-
-int V4L2Gralloc::unlockAllBuffers() {
-  HAL_LOG_ENTER();
-
-  bool failed = false;
-  for (auto const& entry : mBufferMap) {
-    int res = mModule->unlock(mModule, *entry.second->camera_buffer->buffer);
-    if (res) {
-      failed = true;
-    }
-    // When there is a transform to be made, the buffer returned by lock()
-    // is dynamically allocated (to hold the pre-transform data).
-    if (entry.second->transform_dest) {
-      delete[] reinterpret_cast<uint8_t*>(entry.first);
-    }
-    // The BufferData entry is always dynamically allocated in lock().
-    delete entry.second;
-  }
-  mBufferMap.clear();
-
-  // If any unlock failed, return error.
-  if (failed) {
-    return -ENODEV;
-  }
-
-  return 0;
-}
-
-}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/v4l2_gralloc.h b/modules/camera/3_4/v4l2_gralloc.h
deleted file mode 100644
index 82129e3..0000000
--- a/modules/camera/3_4/v4l2_gralloc.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef V4L2_CAMERA_HAL_V4L2_GRALLOC_H_
-#define V4L2_CAMERA_HAL_V4L2_GRALLOC_H_
-
-#include <linux/videodev2.h>
-
-#include <unordered_map>
-
-#include <hardware/camera3.h>
-#include <hardware/gralloc.h>
-#include <system/graphics.h>
-
-namespace v4l2_camera_hal {
-
-// Generously allow up to 6MB (the largest JPEG on the RPi camera is about 5MB).
-static constexpr size_t V4L2_MAX_JPEG_SIZE = 6000000;
-
-// V4L2Gralloc is a wrapper around relevant parts of a gralloc module,
-// with some assistive transformations.
-class V4L2Gralloc {
- public:
-  // Use this method to create V4L2Gralloc objects. Functionally equivalent
-  // to "new V4L2Gralloc", except that it may return nullptr in case of failure.
-  static V4L2Gralloc* NewV4L2Gralloc();
-  virtual ~V4L2Gralloc();
-
-  // Lock a camera buffer. Uses device buffer length, sets user pointer.
-  int lock(const camera3_stream_buffer_t* camera_buffer,
-           uint32_t bytes_per_line,
-           v4l2_buffer* device_buffer);
-  // Unlock a buffer that was locked by this helper (equality determined
-  // based on buffer user pointer, not the specific object).
-  int unlock(const v4l2_buffer* device_buffer);
-  // Release all held locks.
-  int unlockAllBuffers();
-
- private:
-  // Constructor is private to allow failing on bad input.
-  // Use NewV4L2Gralloc instead.
-  V4L2Gralloc(const gralloc_module_t* module);
-
-  const gralloc_module_t* mModule;
-
-  struct BufferData {
-    const camera3_stream_buffer_t* camera_buffer;
-    // Below fields only used when a ycbcr format transform is necessary.
-    std::unique_ptr<android_ycbcr> transform_dest;  // nullptr if no transform.
-    uint32_t v4l2_bytes_per_line;
-  };
-  // Map data pointer : BufferData about that buffer.
-  std::unordered_map<void*, const BufferData*> mBufferMap;
-};
-
-}  // namespace default_camera_hal
-
-#endif  // V4L2_CAMERA_HAL_V4L2_GRALLOC_H_
diff --git a/modules/camera/3_4/v4l2_metadata_factory.cpp b/modules/camera/3_4/v4l2_metadata_factory.cpp
index 3d9cfbb..bc8806f 100644
--- a/modules/camera/3_4/v4l2_metadata_factory.cpp
+++ b/modules/camera/3_4/v4l2_metadata_factory.cpp
@@ -27,7 +27,6 @@
 #include "metadata/partial_metadata_factory.h"
 #include "metadata/property.h"
 #include "metadata/scaling_converter.h"
-#include "v4l2_gralloc.h"
 
 namespace v4l2_camera_hal {
 
@@ -37,6 +36,8 @@
 const int64_t kV4L2ExposureTimeStepNs = 100000;
 // According to spec, each unit of V4L2_CID_ISO_SENSITIVITY is ISO/1000.
 const int32_t kV4L2SensitivityDenominator = 1000;
+// Generously allow up to 6MB (the largest size on the RPi Camera is about 5MB).
+const size_t kV4L2MaxJpegSize = 6000000;
 
 int GetV4L2Metadata(std::shared_ptr<V4L2Wrapper> device,
                     std::unique_ptr<Metadata>* result) {
@@ -433,10 +434,9 @@
       ANDROID_JPEG_THUMBNAIL_SIZE,
       ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
       {{{0, 0}}}));
-  // TODO(b/31022752): Get this from the device,
-  // not constant (from V4L2Gralloc.h).
+  // TODO(b/31022752): Get this from the device, not constant.
   components.insert(std::unique_ptr<PartialMetadataInterface>(
-      new Property<int32_t>(ANDROID_JPEG_MAX_SIZE, V4L2_MAX_JPEG_SIZE)));
+      new Property<int32_t>(ANDROID_JPEG_MAX_SIZE, kV4L2MaxJpegSize)));
   // TODO(b/31021672): Other JPEG controls (GPS, quality, orientation).
   // TODO(b/29939583): V4L2 can only support 1 stream at a time.
   // For now, just reporting minimum allowable for LIMITED devices.
diff --git a/modules/camera/3_4/v4l2_wrapper.cpp b/modules/camera/3_4/v4l2_wrapper.cpp
index 36d0411..d715e7e 100644
--- a/modules/camera/3_4/v4l2_wrapper.cpp
+++ b/modules/camera/3_4/v4l2_wrapper.cpp
@@ -29,12 +29,15 @@
 
 #include <android-base/unique_fd.h>
 
-#include "common.h"
-#include "stream_format.h"
-#include "v4l2_gralloc.h"
+#include "arc/cached_frame.h"
 
 namespace v4l2_camera_hal {
 
+using arc::AllocatedFrameBuffer;
+using arc::SupportedFormat;
+using arc::SupportedFormats;
+using default_camera_hal::CaptureRequest;
+
 const int32_t kStandardSizes[][2] = {
   {4096, 2160}, // 4KDCI (for USB camera)
   {3840, 2160}, // 4KUHD (for USB camera)
@@ -50,20 +53,11 @@
 };
 
 V4L2Wrapper* V4L2Wrapper::NewV4L2Wrapper(const std::string device_path) {
-  std::unique_ptr<V4L2Gralloc> gralloc(V4L2Gralloc::NewV4L2Gralloc());
-  if (!gralloc) {
-    HAL_LOGE("Failed to initialize gralloc helper.");
-    return nullptr;
-  }
-
-  return new V4L2Wrapper(device_path, std::move(gralloc));
+  return new V4L2Wrapper(device_path);
 }
 
-V4L2Wrapper::V4L2Wrapper(const std::string device_path,
-                         std::unique_ptr<V4L2Gralloc> gralloc)
-    : device_path_(std::move(device_path)),
-      gralloc_(std::move(gralloc)),
-      connection_count_(0) {}
+V4L2Wrapper::V4L2Wrapper(const std::string device_path)
+    : device_path_(std::move(device_path)), connection_count_(0) {}
 
 V4L2Wrapper::~V4L2Wrapper() {}
 
@@ -97,6 +91,10 @@
   // (Alternatively, better hotplugging support may make this unecessary
   // by disabling cameras that get disconnected and checking newly connected
   // cameras, so Connect() is never called on an unsupported camera)
+
+  supported_formats_ = GetSupportedFormats();
+  qualified_formats_ = StreamFormat::GetQualifiedFormats(supported_formats_);
+
   return 0;
 }
 
@@ -114,15 +112,16 @@
   --connection_count_;
   if (connection_count_ > 0) {
     HAL_LOGV("Disconnected from camera device %s. %d connections remain.",
-             device_path_.c_str());
+             device_path_.c_str(), connection_count_);
     return;
   }
 
   device_fd_.reset(-1);  // Includes close().
   format_.reset();
-  buffers_.clear();
-  // Closing the device releases all queued buffers back to the user.
-  gralloc_->unlockAllBuffers();
+  {
+    std::lock_guard<std::mutex> buffer_lock(buffer_queue_lock_);
+    buffers_.clear();
+  }
 }
 
 // Helper function. Should be used instead of ioctl throughout this class.
@@ -146,7 +145,7 @@
 
   int32_t type = format_->type();
   if (IoctlLocked(VIDIOC_STREAMON, &type) < 0) {
-    HAL_LOGE("STREAMON fails: %s", strerror(errno));
+    HAL_LOGE("STREAMON fails (%d): %s", errno, strerror(errno));
     return -ENODEV;
   }
 
@@ -164,20 +163,16 @@
   int32_t type = format_->type();
   int res = IoctlLocked(VIDIOC_STREAMOFF, &type);
   // Calling STREAMOFF releases all queued buffers back to the user.
-  int gralloc_res = gralloc_->unlockAllBuffers();
   // No buffers in flight.
-  for (size_t i = 0; i < buffers_.size(); ++i) {
-    buffers_[i] = false;
-  }
   if (res < 0) {
     HAL_LOGE("STREAMOFF fails: %s", strerror(errno));
     return -ENODEV;
   }
-  if (gralloc_res < 0) {
-    HAL_LOGE("Failed to unlock all buffers after turning stream off.");
-    return gralloc_res;
+  std::lock_guard<std::mutex> lock(buffer_queue_lock_);
+  for (auto& buffer : buffers_) {
+    buffer.active = false;
+    buffer.request.reset();
   }
-
   HAL_LOGV("Stream turned off.");
   return 0;
 }
@@ -316,6 +311,36 @@
   return 0;
 }
 
+const SupportedFormats V4L2Wrapper::GetSupportedFormats() {
+  SupportedFormats formats;
+  std::set<uint32_t> pixel_formats;
+  int res = GetFormats(&pixel_formats);
+  if (res) {
+    HAL_LOGE("Failed to get device formats.");
+    return formats;
+  }
+
+  arc::SupportedFormat supported_format;
+  std::set<std::array<int32_t, 2>> frame_sizes;
+
+  for (auto pixel_format : pixel_formats) {
+    supported_format.fourcc = pixel_format;
+
+    frame_sizes.clear();
+    res = GetFormatFrameSizes(pixel_format, &frame_sizes);
+    if (res) {
+      HAL_LOGE("Failed to get frame sizes for format: 0x%x", pixel_format);
+      continue;
+    }
+    for (auto frame_size : frame_sizes) {
+      supported_format.width = frame_size[0];
+      supported_format.height = frame_size[1];
+      formats.push_back(supported_format);
+    }
+  }
+  return formats;
+}
+
 int V4L2Wrapper::GetFormats(std::set<uint32_t>* v4l2_formats) {
   HAL_LOG_ENTER();
 
@@ -336,6 +361,22 @@
   return 0;
 }
 
+int V4L2Wrapper::GetQualifiedFormats(std::vector<uint32_t>* v4l2_formats) {
+  HAL_LOG_ENTER();
+  if (!connected()) {
+    HAL_LOGE(
+        "Device is not connected, qualified formats may not have been set.");
+    return -EINVAL;
+  }
+  v4l2_formats->clear();
+  std::set<uint32_t> unique_fourccs;
+  for (auto& format : qualified_formats_) {
+    unique_fourccs.insert(format.fourcc);
+  }
+  v4l2_formats->assign(unique_fourccs.begin(), unique_fourccs.end());
+  return 0;
+}
+
 int V4L2Wrapper::GetFormatFrameSizes(uint32_t v4l2_format,
                                      std::set<std::array<int32_t, 2>>* sizes) {
   v4l2_frmsizeenum size_query;
@@ -459,8 +500,6 @@
     return 0;
   }
 
-  // Not in the correct format, set the new one.
-
   if (format_) {
     // If we had an old format, first request 0 buffers to inform the device
     // we're no longer using any previously "allocated" buffers from the old
@@ -473,19 +512,34 @@
     }
   }
 
+  // Select the matching format, or if not available, select a qualified format
+  // we can convert from.
+  SupportedFormat format;
+  if (!StreamFormat::FindBestFitFormat(supported_formats_, qualified_formats_,
+                                       desired_format.v4l2_pixel_format(),
+                                       desired_format.width(),
+                                       desired_format.height(), &format)) {
+    HAL_LOGE(
+        "Unable to find supported resolution in list, "
+        "width: %d, height: %d",
+        desired_format.width(), desired_format.height());
+    return -EINVAL;
+  }
+
   // Set the camera to the new format.
   v4l2_format new_format;
-  desired_format.FillFormatRequest(&new_format);
+  const StreamFormat resolved_format(format);
+  resolved_format.FillFormatRequest(&new_format);
+
   // TODO(b/29334616): When async, this will need to check if the stream
   // is on, and if so, lock it off while setting format.
-
   if (IoctlLocked(VIDIOC_S_FMT, &new_format) < 0) {
     HAL_LOGE("S_FMT failed: %s", strerror(errno));
     return -ENODEV;
   }
 
   // Check that the driver actually set to the requested values.
-  if (desired_format != new_format) {
+  if (resolved_format != new_format) {
     HAL_LOGE("Device doesn't support desired stream configuration.");
     return -EINVAL;
   }
@@ -512,28 +566,22 @@
 
   int res = IoctlLocked(VIDIOC_REQBUFS, &req_buffers);
   // Calling REQBUFS releases all queued buffers back to the user.
-  int gralloc_res = gralloc_->unlockAllBuffers();
   if (res < 0) {
     HAL_LOGE("REQBUFS failed: %s", strerror(errno));
     return -ENODEV;
   }
-  if (gralloc_res < 0) {
-    HAL_LOGE("Failed to unlock all buffers when setting up new buffers.");
-    return gralloc_res;
-  }
 
   // V4L2 will set req_buffers.count to a number of buffers it can handle.
   if (num_requested > 0 && req_buffers.count < 1) {
     HAL_LOGE("REQBUFS claims it can't handle any buffers.");
     return -ENODEV;
   }
-  buffers_.resize(req_buffers.count, false);
-
+  buffers_.resize(req_buffers.count);
   return 0;
 }
 
-int V4L2Wrapper::EnqueueBuffer(const camera3_stream_buffer_t* camera_buffer,
-                               uint32_t* enqueued_index) {
+int V4L2Wrapper::EnqueueRequest(
+    std::shared_ptr<default_camera_hal::CaptureRequest> request) {
   if (!format_) {
     HAL_LOGE("Stream format must be set before enqueuing buffers.");
     return -ENODEV;
@@ -545,9 +593,8 @@
   int index = -1;
   {
     std::lock_guard<std::mutex> guard(buffer_queue_lock_);
-    for (int i = 0; i < buffers_.size(); ++i) {
-      if (!buffers_[i]) {
-        buffers_[i] = true;
+    for (size_t i = 0; i < buffers_.size(); ++i) {
+      if (!buffers_[i].active) {
         index = i;
         break;
       }
@@ -572,36 +619,40 @@
     HAL_LOGE("QUERYBUF fails: %s", strerror(errno));
     // Return buffer index.
     std::lock_guard<std::mutex> guard(buffer_queue_lock_);
-    buffers_[index] = false;
+    buffers_[index].active = false;
     return -ENODEV;
   }
 
-  // Lock the buffer for writing (fills in the user pointer field).
-  int res =
-      gralloc_->lock(camera_buffer, format_->bytes_per_line(), &device_buffer);
-  if (res) {
-    HAL_LOGE("Gralloc failed to lock buffer.");
-    // Return buffer index.
+  // Setup our request context and fill in the user pointer field.
+  RequestContext* request_context;
+  void* data;
+  {
     std::lock_guard<std::mutex> guard(buffer_queue_lock_);
-    buffers_[index] = false;
-    return res;
+    request_context = &buffers_[index];
+    request_context->camera_buffer->SetDataSize(device_buffer.length);
+    request_context->camera_buffer->Reset();
+    request_context->camera_buffer->SetFourcc(format_->v4l2_pixel_format());
+    request_context->camera_buffer->SetWidth(format_->width());
+    request_context->camera_buffer->SetHeight(format_->height());
+    request_context->request = request;
+    data = request_context->camera_buffer->GetData();
   }
+  device_buffer.m.userptr = reinterpret_cast<unsigned long>(data);
+
+  // Pass the buffer to the camera.
   if (IoctlLocked(VIDIOC_QBUF, &device_buffer) < 0) {
     HAL_LOGE("QBUF fails: %s", strerror(errno));
-    gralloc_->unlock(&device_buffer);
-    // Return buffer index.
-    std::lock_guard<std::mutex> guard(buffer_queue_lock_);
-    buffers_[index] = false;
     return -ENODEV;
   }
 
-  if (enqueued_index) {
-    *enqueued_index = index;
-  }
+  // Mark the buffer as in flight.
+  std::lock_guard<std::mutex> guard(buffer_queue_lock_);
+  request_context->active = true;
+
   return 0;
 }
 
-int V4L2Wrapper::DequeueBuffer(uint32_t* dequeued_index) {
+int V4L2Wrapper::DequeueRequest(std::shared_ptr<CaptureRequest>* request) {
   if (!format_) {
     HAL_LOGV(
         "Format not set, so stream can't be on, "
@@ -625,23 +676,63 @@
     }
   }
 
-  // Mark the buffer as no longer in flight.
-  {
-    std::lock_guard<std::mutex> guard(buffer_queue_lock_);
-    buffers_[buffer.index] = false;
+  std::lock_guard<std::mutex> guard(buffer_queue_lock_);
+  RequestContext* request_context = &buffers_[buffer.index];
+
+  // Lock the camera stream buffer for painting.
+  const camera3_stream_buffer_t* stream_buffer =
+      &request_context->request->output_buffers[0];
+  uint32_t fourcc =
+      StreamFormat::HalToV4L2PixelFormat(stream_buffer->stream->format);
+
+  if (request) {
+    *request = request_context->request;
   }
 
-  // Now that we're done painting the buffer, we can unlock it.
-  res = gralloc_->unlock(&buffer);
+  // Note that the device buffer length is passed to the output frame. If the
+  // GrallocFrameBuffer does not have support for the transformation to
+  // |fourcc|, it will assume that the amount of data to lock is based on
+  // |buffer.length|, otherwise it will use the ImageProcessor::ConvertedSize.
+  arc::GrallocFrameBuffer output_frame(
+      *stream_buffer->buffer, stream_buffer->stream->width,
+      stream_buffer->stream->height, fourcc, buffer.length,
+      stream_buffer->stream->usage);
+  res = output_frame.Map();
   if (res) {
-    HAL_LOGE("Gralloc failed to unlock buffer after dequeueing.");
-    return res;
+    HAL_LOGE("Failed to map output frame.");
+    request_context->request.reset();
+    return -EINVAL;
+  }
+  if (request_context->camera_buffer->GetFourcc() == fourcc &&
+      request_context->camera_buffer->GetWidth() ==
+          stream_buffer->stream->width &&
+      request_context->camera_buffer->GetHeight() ==
+          stream_buffer->stream->height) {
+    // If no format conversion needs to be applied, directly copy the data over.
+    memcpy(output_frame.GetData(), request_context->camera_buffer->GetData(),
+           request_context->camera_buffer->GetDataSize());
+  } else {
+    // Perform the format conversion.
+    arc::CachedFrame cached_frame;
+    cached_frame.SetSource(request_context->camera_buffer.get(), 0);
+    cached_frame.Convert(request_context->request->settings, &output_frame);
   }
 
-  if (dequeued_index) {
-    *dequeued_index = buffer.index;
-  }
+  request_context->request.reset();
+  // Mark the buffer as not in flight.
+  request_context->active = false;
   return 0;
 }
 
+int V4L2Wrapper::GetInFlightBufferCount() {
+  int count = 0;
+  std::lock_guard<std::mutex> guard(buffer_queue_lock_);
+  for (auto& buffer : buffers_) {
+    if (buffer.active) {
+      count++;
+    }
+  }
+  return count;
+}
+
 }  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_wrapper.h b/modules/camera/3_4/v4l2_wrapper.h
index c3ad272..4a8d517 100644
--- a/modules/camera/3_4/v4l2_wrapper.h
+++ b/modules/camera/3_4/v4l2_wrapper.h
@@ -26,11 +26,14 @@
 
 #include <android-base/unique_fd.h>
 
+#include "arc/common_types.h"
+#include "arc/frame_buffer.h"
+#include "capture_request.h"
 #include "common.h"
 #include "stream_format.h"
-#include "v4l2_gralloc.h"
 
 namespace v4l2_camera_hal {
+
 class V4L2Wrapper {
  public:
   // Use this method to create V4L2Wrapper objects. Functionally equivalent
@@ -67,8 +70,10 @@
                          int32_t* result = nullptr);
   // Manage format.
   virtual int GetFormats(std::set<uint32_t>* v4l2_formats);
+  virtual int GetQualifiedFormats(std::vector<uint32_t>* v4l2_formats);
   virtual int GetFormatFrameSizes(uint32_t v4l2_format,
                                   std::set<std::array<int32_t, 2>>* sizes);
+
   // Durations are returned in ns.
   virtual int GetFormatFrameDurationRange(
       uint32_t v4l2_format,
@@ -77,15 +82,16 @@
   virtual int SetFormat(const StreamFormat& desired_format,
                         uint32_t* result_max_buffers);
   // Manage buffers.
-  virtual int EnqueueBuffer(const camera3_stream_buffer_t* camera_buffer,
-                            uint32_t* enqueued_index = nullptr);
-  virtual int DequeueBuffer(uint32_t* dequeued_index = nullptr);
+  virtual int EnqueueRequest(
+      std::shared_ptr<default_camera_hal::CaptureRequest> request);
+  virtual int DequeueRequest(
+      std::shared_ptr<default_camera_hal::CaptureRequest>* request);
+  virtual int GetInFlightBufferCount();
 
  private:
   // Constructor is private to allow failing on bad input.
   // Use NewV4L2Wrapper instead.
-  V4L2Wrapper(const std::string device_path,
-              std::unique_ptr<V4L2Gralloc> gralloc);
+  V4L2Wrapper(const std::string device_path);
 
   // Connect or disconnect to the device. Access by creating/destroying
   // a V4L2Wrapper::Connection object.
@@ -99,20 +105,19 @@
 
   inline bool connected() { return device_fd_.get() >= 0; }
 
+  // Format management.
+  const arc::SupportedFormats GetSupportedFormats();
+
   // The camera device path. For example, /dev/video0.
   const std::string device_path_;
   // The opened device fd.
   android::base::unique_fd device_fd_;
   // The underlying gralloc module.
-  std::unique_ptr<V4L2Gralloc> gralloc_;
+  // std::unique_ptr<V4L2Gralloc> gralloc_;
   // Whether or not the device supports the extended control query.
   bool extended_query_supported_;
   // The format this device is set up for.
   std::unique_ptr<StreamFormat> format_;
-  // Map indecies to buffer status. True if the index is in-flight.
-  // |buffers_.size()| will always be the maximum number of buffers this device
-  // can handle in its current format.
-  std::vector<bool> buffers_;
   // Lock protecting use of the buffer tracker.
   std::mutex buffer_queue_lock_;
   // Lock protecting use of the device.
@@ -121,6 +126,28 @@
   std::mutex connection_lock_;
   // Reference count connections.
   int connection_count_;
+  // Supported formats.
+  arc::SupportedFormats supported_formats_;
+  // Qualified formats.
+  arc::SupportedFormats qualified_formats_;
+
+  class RequestContext {
+   public:
+    RequestContext()
+        : active(false),
+          camera_buffer(std::make_shared<arc::AllocatedFrameBuffer>(0)){};
+    ~RequestContext(){};
+    // Indicates whether this request context is in use.
+    bool active;
+    // Buffer handles of the context.
+    std::shared_ptr<arc::AllocatedFrameBuffer> camera_buffer;
+    std::shared_ptr<default_camera_hal::CaptureRequest> request;
+  };
+
+  // Map of in flight requests.
+  // |buffers_.size()| will always be the maximum number of buffers this device
+  // can handle in its current format.
+  std::vector<RequestContext> buffers_;
 
   friend class Connection;
   friend class V4L2WrapperMock;
diff --git a/modules/camera/3_4/v4l2_wrapper_mock.h b/modules/camera/3_4/v4l2_wrapper_mock.h
index d423cc5..f98bc94 100644
--- a/modules/camera/3_4/v4l2_wrapper_mock.h
+++ b/modules/camera/3_4/v4l2_wrapper_mock.h
@@ -27,7 +27,7 @@
 
 class V4L2WrapperMock : public V4L2Wrapper {
  public:
-  V4L2WrapperMock() : V4L2Wrapper("", nullptr){};
+  V4L2WrapperMock() : V4L2Wrapper(""){};
   MOCK_METHOD0(StreamOn, int());
   MOCK_METHOD0(StreamOff, int());
   MOCK_METHOD2(QueryControl,
@@ -36,17 +36,15 @@
   MOCK_METHOD3(SetControl,
                int(uint32_t control_id, int32_t desired, int32_t* result));
   MOCK_METHOD1(GetFormats, int(std::set<uint32_t>*));
+  MOCK_METHOD1(GetQualifiedFormats, int(std::vector<uint32_t>*));
   MOCK_METHOD2(GetFormatFrameSizes,
                int(uint32_t, std::set<std::array<int32_t, 2>>*));
   MOCK_METHOD3(GetFormatFrameDurationRange,
                int(uint32_t,
                    const std::array<int32_t, 2>&,
                    std::array<int64_t, 2>*));
-  MOCK_METHOD4(SetFormat,
-               int(int format,
-                   uint32_t width,
-                   uint32_t height,
-                   uint32_t* result_max_buffers));
+  MOCK_METHOD2(SetFormat, int(const StreamFormat& desired_format,
+                              uint32_t* result_max_buffers));
   MOCK_METHOD2(EnqueueBuffer,
                int(const camera3_stream_buffer_t* camera_buffer,
                    uint32_t* enqueued_index));