Add format querying.
Query formats, sizes, and frame rates.
BUG: https://b/29394024, https://b/30605346
TEST: Test java program shows a bunch of formats & rates,
can take pictures in different sizes now.
Change-Id: I203fd8f200dda79cf6946c58f130548b05269fbd
diff --git a/modules/camera/3_4/v4l2_wrapper.cpp b/modules/camera/3_4/v4l2_wrapper.cpp
index d331a77..7d8e38c 100644
--- a/modules/camera/3_4/v4l2_wrapper.cpp
+++ b/modules/camera/3_4/v4l2_wrapper.cpp
@@ -16,6 +16,10 @@
#include "v4l2_wrapper.h"
+#include <algorithm>
+#include <limits>
+#include <vector>
+
#include <fcntl.h>
#include <linux/videodev2.h>
#include <sys/stat.h>
@@ -32,6 +36,9 @@
namespace v4l2_camera_hal {
+const std::vector<std::array<int32_t, 2>> kStandardSizes(
+ {{{1920, 1080}}, {{1280, 720}}, {{640, 480}}, {{320, 240}}});
+
V4L2Wrapper* V4L2Wrapper::NewV4L2Wrapper(const std::string device_path) {
HAL_LOG_ENTER();
@@ -48,7 +55,8 @@
std::unique_ptr<V4L2Gralloc> gralloc)
: device_path_(std::move(device_path)),
gralloc_(std::move(gralloc)),
- max_buffers_(0) {
+ max_buffers_(0),
+ connection_count_(0) {
HAL_LOG_ENTER();
}
@@ -56,28 +64,26 @@
int V4L2Wrapper::Connect() {
HAL_LOG_ENTER();
- std::lock_guard<std::mutex> lock(device_lock_);
+ std::lock_guard<std::mutex> lock(connection_lock_);
if (connected()) {
- HAL_LOGE("Camera device %s is already connected. Close it first",
- device_path_.c_str());
- return -EIO;
+ HAL_LOGV("Camera device %s is already connected.", device_path_.c_str());
+ ++connection_count_;
+ return 0;
}
int fd = TEMP_FAILURE_RETRY(open(device_path_.c_str(), O_RDWR));
if (fd < 0) {
HAL_LOGE("failed to open %s (%s)", device_path_.c_str(), strerror(errno));
- return -errno;
+ return -ENODEV;
}
device_fd_.reset(fd);
+ ++connection_count_;
// Check if this connection has the extended control query capability.
v4l2_query_ext_ctrl query;
query.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
- // Already holding the lock, so don't call IoctlLocked.
- int res = TEMP_FAILURE_RETRY(
- ioctl(device_fd_.get(), VIDIOC_QUERY_EXT_CTRL, &query));
- extended_query_supported_ = (res == 0);
+ extended_query_supported_ = (IoctlLocked(VIDIOC_QUERY_EXT_CTRL, &query) == 0);
// TODO(b/29185945): confirm this is a supported device.
// This is checked by the HAL, but the device at device_path_ may
@@ -90,7 +96,21 @@
void V4L2Wrapper::Disconnect() {
HAL_LOG_ENTER();
- std::lock_guard<std::mutex> lock(device_lock_);
+ std::lock_guard<std::mutex> lock(connection_lock_);
+
+ if (connection_count_ == 0) {
+ // Not connected.
+ HAL_LOGE("Camera device %s is not connected, cannot disconnect.",
+ device_path_.c_str(), connection_count_);
+ return;
+ }
+
+ --connection_count_;
+ if (connection_count_ > 0) {
+ HAL_LOGV("Disconnected from camera device %s. %d connections remain.",
+ device_path_.c_str(), connection_count_);
+ return;
+ }
device_fd_.reset(); // Includes close().
format_.reset();
@@ -102,7 +122,7 @@
// Helper function. Should be used instead of ioctl throughout this class.
template <typename T>
int V4L2Wrapper::IoctlLocked(int request, T data) {
- HAL_LOG_ENTER();
+ // Potentially called so many times logging entry is a bad idea.
std::lock_guard<std::mutex> lock(device_lock_);
if (!connected()) {
@@ -247,6 +267,136 @@
return 0;
}
+int V4L2Wrapper::GetFormats(std::set<uint32_t>* v4l2_formats) {
+ HAL_LOG_ENTER();
+
+ v4l2_fmtdesc format_query;
+ memset(&format_query, 0, sizeof(format_query));
+ // TODO(b/30000211): multiplanar support.
+ format_query.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ while (IoctlLocked(VIDIOC_ENUM_FMT, &format_query) >= 0) {
+ v4l2_formats->insert(format_query.pixelformat);
+ ++format_query.index;
+ }
+
+ if (errno != EINVAL) {
+ HAL_LOGE("ENUM_FMT fails at index %d: %s", format_query.index,
+ strerror(errno));
+ return -ENODEV;
+ }
+ return 0;
+}
+
+int V4L2Wrapper::GetFormatFrameSizes(uint32_t v4l2_format,
+ std::set<std::array<int32_t, 2>>* sizes) {
+ HAL_LOG_ENTER();
+
+ v4l2_frmsizeenum size_query;
+ memset(&size_query, 0, sizeof(size_query));
+ size_query.pixel_format = v4l2_format;
+ if (IoctlLocked(VIDIOC_ENUM_FRAMESIZES, &size_query) < 0) {
+ HAL_LOGE("ENUM_FRAMESIZES failed: %s", strerror(errno));
+ return -ENODEV;
+ }
+ if (size_query.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ // Discrete: enumerate all sizes using VIDIOC_ENUM_FRAMESIZES.
+ // Assuming that a driver with discrete frame sizes has a reasonable number
+ // of them.
+ do {
+ sizes->insert({{{static_cast<int32_t>(size_query.discrete.width),
+ static_cast<int32_t>(size_query.discrete.height)}}});
+ ++size_query.index;
+ } while (IoctlLocked(VIDIOC_ENUM_FRAMESIZES, &size_query) >= 0);
+ if (errno != EINVAL) {
+ HAL_LOGE("ENUM_FRAMESIZES fails at index %d: %s", size_query.index,
+ strerror(errno));
+ return -ENODEV;
+ }
+ } else {
+ // Continuous/Step-wise: based on the stepwise struct returned by the query.
+ // Fully listing all possible sizes, with large enough range/small enough
+ // step size, may produce far too many potential sizes. Instead, find the
+ // closest to a set of standard sizes plus largest possible.
+ sizes->insert({{{static_cast<int32_t>(size_query.stepwise.max_width),
+ static_cast<int32_t>(size_query.stepwise.max_height)}}});
+ for (const auto& size : kStandardSizes) {
+ // Find the closest size, rounding up.
+ uint32_t desired_width = size[0];
+ uint32_t desired_height = size[1];
+ if (desired_width < size_query.stepwise.min_width ||
+ desired_height < size_query.stepwise.min_height) {
+ HAL_LOGV("Standard size %u x %u is too small for format %d",
+ desired_width, desired_height, v4l2_format);
+ continue;
+ } else if (desired_width > size_query.stepwise.max_width &&
+ desired_height > size_query.stepwise.max_height) {
+ HAL_LOGV("Standard size %u x %u is too big for format %d",
+ desired_width, desired_height, v4l2_format);
+ continue;
+ }
+
+ // Round up.
+ uint32_t width_steps = (desired_width - size_query.stepwise.min_width +
+ size_query.stepwise.step_width - 1) /
+ size_query.stepwise.step_width;
+ uint32_t height_steps = (desired_height - size_query.stepwise.min_height +
+ size_query.stepwise.step_height - 1) /
+ size_query.stepwise.step_height;
+ sizes->insert(
+ {{{static_cast<int32_t>(size_query.stepwise.min_width +
+ width_steps * size_query.stepwise.step_width),
+ static_cast<int32_t>(size_query.stepwise.min_height +
+ height_steps *
+ size_query.stepwise.step_height)}}});
+ }
+ }
+ return 0;
+}
+
+// Converts a v4l2_fract with units of seconds to an int64_t with units of ns.
+inline int64_t FractToNs(const v4l2_fract& fract) {
+ return (1000000000LL * fract.numerator) / fract.denominator;
+}
+
+int V4L2Wrapper::GetFormatFrameDurationRange(
+ uint32_t v4l2_format, const std::array<int32_t, 2>& size,
+ std::array<int64_t, 2>* duration_range) {
+ // Potentially called so many times logging entry is a bad idea.
+
+ v4l2_frmivalenum duration_query;
+ memset(&duration_query, 0, sizeof(duration_query));
+ duration_query.pixel_format = v4l2_format;
+ duration_query.width = size[0];
+ duration_query.height = size[1];
+ if (IoctlLocked(VIDIOC_ENUM_FRAMEINTERVALS, &duration_query) < 0) {
+ HAL_LOGE("ENUM_FRAMEINTERVALS failed: %s", strerror(errno));
+ return -ENODEV;
+ }
+
+ int64_t min = std::numeric_limits<int64_t>::max();
+ int64_t max = std::numeric_limits<int64_t>::min();
+ if (duration_query.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ // Discrete: enumerate all durations using VIDIOC_ENUM_FRAMEINTERVALS.
+ do {
+ min = std::min(min, FractToNs(duration_query.discrete));
+ max = std::max(max, FractToNs(duration_query.discrete));
+ ++duration_query.index;
+ } while (IoctlLocked(VIDIOC_ENUM_FRAMEINTERVALS, &duration_query) >= 0);
+ if (errno != EINVAL) {
+ HAL_LOGE("ENUM_FRAMEINTERVALS fails at index %d: %s",
+ duration_query.index, strerror(errno));
+ return -ENODEV;
+ }
+ } else {
+ // Continuous/Step-wise: simply convert the given min and max.
+ min = FractToNs(duration_query.stepwise.min);
+ max = FractToNs(duration_query.stepwise.max);
+ }
+ (*duration_range)[0] = min;
+ (*duration_range)[1] = max;
+ return 0;
+}
+
int V4L2Wrapper::SetFormat(const default_camera_hal::Stream& stream,
uint32_t* result_max_buffers) {
HAL_LOG_ENTER();
@@ -342,20 +492,19 @@
v4l2_buffer device_buffer;
memset(&device_buffer, 0, sizeof(device_buffer));
device_buffer.type = format_->type();
+ // TODO(b/29334616): when this is async, actually limit the number
+ // of buffers used to the known max, and set this according to the
+ // queue length.
+ device_buffer.index = 0;
- // Use QUERYBUF to ensure our buffer/device is in good shape.
+ // Use QUERYBUF to ensure our buffer/device is in good shape,
+ // and fill out remaining fields.
if (IoctlLocked(VIDIOC_QUERYBUF, &device_buffer) < 0) {
HAL_LOGE("QUERYBUF fails: %s", strerror(errno));
return -ENODEV;
}
- // Configure the device buffer based on the stream buffer.
- device_buffer.memory = V4L2_MEMORY_USERPTR;
- // TODO(b/29334616): when this is async, actually limit the number
- // of buffers used to the known max, and set this according to the
- // queue length.
- device_buffer.index = 0;
- // Lock the buffer for writing.
+ // 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) {
@@ -363,7 +512,7 @@
return res;
}
if (IoctlLocked(VIDIOC_QBUF, &device_buffer) < 0) {
- HAL_LOGE("QBUF (%d) fails: %s", 0, strerror(errno));
+ HAL_LOGE("QBUF fails: %s", strerror(errno));
gralloc_->unlock(&device_buffer);
return -ENODEV;
}