Camera: add external camera fps control
V4L2 doesn't seem to allow changing fps at runtime, so we need to
wait until pipeline is idle and reconfigure V4L2 streams.
Also update the fps filtering logic to make sure supported fps
are consistent as long as the major dimension is the same.
Ex: for 4:3 webcams, all 1280*N sizes has the same fps set, or
for 16:9 webcams, all N*720 sizes has the same fps set.
Bug: 72261912
Change-Id: I534f09bebadb2f532a030a841633a8be7d3a87cc
diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
index 5346f80..01d7371 100644
--- a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
+++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
@@ -160,7 +160,6 @@
SupportedV4L2Format streamingFmt;
std::unordered_set<uint32_t> inflightFrames;
{
- Mutex::Autolock _l(mLock);
bool sessionLocked = tryLock(mLock);
if (!sessionLocked) {
dprintf(fd, "!! ExternalCameraDeviceSession mLock may be deadlocked !!\n");
@@ -180,12 +179,13 @@
streaming ? "streaming" : "not streaming");
if (streaming) {
// TODO: dump fps later
- dprintf(fd, "Current V4L2 format %c%c%c%c %dx%d\n",
+ dprintf(fd, "Current V4L2 format %c%c%c%c %dx%d @ %ffps\n",
streamingFmt.fourcc & 0xFF,
(streamingFmt.fourcc >> 8) & 0xFF,
(streamingFmt.fourcc >> 16) & 0xFF,
(streamingFmt.fourcc >> 24) & 0xFF,
- streamingFmt.width, streamingFmt.height);
+ streamingFmt.width, streamingFmt.height,
+ mV4l2StreamingFps);
size_t numDequeuedV4l2Buffers = 0;
{
@@ -291,7 +291,6 @@
config_v32.streams[i] = requestedConfiguration.streams[i].v3_2;
}
- // Ignore requestedConfiguration.sessionParams. External camera does not support it
Status status = configureStreams(config_v32, &outStreams_v33);
V3_4::HalStreamConfiguration outStreams;
@@ -451,6 +450,23 @@
}
}
+int ExternalCameraDeviceSession::waitForV4L2BufferReturnLocked(std::unique_lock<std::mutex>& lk) {
+ std::chrono::seconds timeout = std::chrono::seconds(kBufferWaitTimeoutSec);
+ mLock.unlock();
+ auto st = mV4L2BufferReturned.wait_for(lk, timeout);
+ // Here we introduce a order where mV4l2BufferLock is acquired before mLock, while
+ // the normal lock acquisition order is reversed. This is fine because in most of
+ // cases we are protected by mInterfaceLock. The only thread that can cause deadlock
+ // is the OutputThread, where we do need to make sure we don't acquire mLock then
+ // mV4l2BufferLock
+ mLock.lock();
+ if (st == std::cv_status::timeout) {
+ ALOGE("%s: wait for V4L2 buffer return timeout!", __FUNCTION__);
+ return -1;
+ }
+ return 0;
+}
+
Status ExternalCameraDeviceSession::processOneCaptureRequest(const CaptureRequest& request) {
Status status = initStatus();
if (status != Status::OK) {
@@ -510,15 +526,59 @@
return Status::ILLEGAL_ARGUMENT;
}
+ camera_metadata_entry fpsRange = mLatestReqSetting.find(ANDROID_CONTROL_AE_TARGET_FPS_RANGE);
+ if (fpsRange.count == 2) {
+ double requestFpsMax = fpsRange.data.i32[1];
+ double closestFps = 0.0;
+ double fpsError = 1000.0;
+ bool fpsSupported = false;
+ for (const auto& fr : mV4l2StreamingFmt.frameRates) {
+ double f = fr.getDouble();
+ if (std::fabs(requestFpsMax - f) < 1.0) {
+ fpsSupported = true;
+ break;
+ }
+ if (std::fabs(requestFpsMax - f) < fpsError) {
+ fpsError = std::fabs(requestFpsMax - f);
+ closestFps = f;
+ }
+ }
+ if (!fpsSupported) {
+ /* This can happen in a few scenarios:
+ * 1. The application is sending a FPS range not supported by the configured outputs.
+ * 2. The application is sending a valid FPS range for all cofigured outputs, but
+ * the selected V4L2 size can only run at slower speed. This should be very rare
+ * though: for this to happen a sensor needs to support at least 3 different aspect
+ * ratio outputs, and when (at least) two outputs are both not the main aspect ratio
+ * of the webcam, a third size that's larger might be picked and runs into this
+ * issue.
+ */
+ ALOGW("%s: cannot reach fps %d! Will do %f instead",
+ __FUNCTION__, fpsRange.data.i32[1], closestFps);
+ requestFpsMax = closestFps;
+ }
+
+ if (requestFpsMax != mV4l2StreamingFps) {
+ {
+ std::unique_lock<std::mutex> lk(mV4l2BufferLock);
+ while (mNumDequeuedV4l2Buffers != 0) {
+ // Wait until pipeline is idle before reconfigure stream
+ int waitRet = waitForV4L2BufferReturnLocked(lk);
+ if (waitRet != 0) {
+ ALOGE("%s: wait for pipeline idle failed!", __FUNCTION__);
+ return Status::INTERNAL_ERROR;
+ }
+ }
+ }
+ configureV4l2StreamLocked(mV4l2StreamingFmt, requestFpsMax);
+ }
+ }
+
status = importRequest(request, allBufPtrs, allFences);
if (status != Status::OK) {
return status;
}
- // TODO: program fps range per capture request here
- // or limit the set of availableFpsRange
-
-
nsecs_t shutterTs = 0;
sp<V4L2Frame> frameIn = dequeueV4l2FrameLocked(&shutterTs);
if ( frameIn == nullptr) {
@@ -1979,7 +2039,46 @@
return OK;
}
-int ExternalCameraDeviceSession::configureV4l2StreamLocked(const SupportedV4L2Format& v4l2Fmt) {
+int ExternalCameraDeviceSession::setV4l2FpsLocked(double fps) {
+ // VIDIOC_G_PARM/VIDIOC_S_PARM: set fps
+ v4l2_streamparm streamparm = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
+ // The following line checks that the driver knows about framerate get/set.
+ int ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_G_PARM, &streamparm));
+ if (ret != 0) {
+ if (errno == -EINVAL) {
+ ALOGW("%s: device does not support VIDIOC_G_PARM", __FUNCTION__);
+ }
+ return -errno;
+ }
+ // Now check if the device is able to accept a capture framerate set.
+ if (!(streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) {
+ ALOGW("%s: device does not support V4L2_CAP_TIMEPERFRAME", __FUNCTION__);
+ return -EINVAL;
+ }
+
+ // fps is float, approximate by a fraction.
+ const int kFrameRatePrecision = 10000;
+ streamparm.parm.capture.timeperframe.numerator = kFrameRatePrecision;
+ streamparm.parm.capture.timeperframe.denominator =
+ (fps * kFrameRatePrecision);
+
+ if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_PARM, &streamparm)) < 0) {
+ ALOGE("%s: failed to set framerate to %f: %s", __FUNCTION__, fps, strerror(errno));
+ return -1;
+ }
+
+ double retFps = streamparm.parm.capture.timeperframe.denominator /
+ static_cast<double>(streamparm.parm.capture.timeperframe.numerator);
+ if (std::fabs(fps - retFps) > 1.0) {
+ ALOGE("%s: expect fps %f, got %f instead", __FUNCTION__, fps, retFps);
+ return -1;
+ }
+ mV4l2StreamingFps = fps;
+ return 0;
+}
+
+int ExternalCameraDeviceSession::configureV4l2StreamLocked(
+ const SupportedV4L2Format& v4l2Fmt, double requestFps) {
int ret = v4l2StreamOffLocked();
if (ret != OK) {
ALOGE("%s: stop v4l2 streaming failed: ret %d", __FUNCTION__, ret);
@@ -2016,46 +2115,31 @@
uint32_t bufferSize = fmt.fmt.pix.sizeimage;
ALOGI("%s: V4L2 buffer size is %d", __FUNCTION__, bufferSize);
- float maxFps = -1.f;
- float fps = 1000.f;
- const float kDefaultFps = 30.f;
- // Try to pick the slowest fps that is at least 30
- for (const auto& fr : v4l2Fmt.frameRates) {
- double f = fr.getDouble();
- if (maxFps < f) {
- maxFps = f;
- }
- if (f >= kDefaultFps && f < fps) {
- fps = f;
- }
- }
- if (fps == 1000.f) {
- fps = maxFps;
- }
-
- // VIDIOC_G_PARM/VIDIOC_S_PARM: set fps
- v4l2_streamparm streamparm = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
- // The following line checks that the driver knows about framerate get/set.
- if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_G_PARM, &streamparm)) >= 0) {
- // Now check if the device is able to accept a capture framerate set.
- if (streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) {
- // |frame_rate| is float, approximate by a fraction.
- const int kFrameRatePrecision = 10000;
- streamparm.parm.capture.timeperframe.numerator = kFrameRatePrecision;
- streamparm.parm.capture.timeperframe.denominator =
- (fps * kFrameRatePrecision);
-
- if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_PARM, &streamparm)) < 0) {
- ALOGE("%s: failed to set framerate to %f", __FUNCTION__, fps);
- return UNKNOWN_ERROR;
+ const double kDefaultFps = 30.0;
+ double fps = 1000.0;
+ if (requestFps != 0.0) {
+ fps = requestFps;
+ } else {
+ double maxFps = -1.0;
+ // Try to pick the slowest fps that is at least 30
+ for (const auto& fr : v4l2Fmt.frameRates) {
+ double f = fr.getDouble();
+ if (maxFps < f) {
+ maxFps = f;
+ }
+ if (f >= kDefaultFps && f < fps) {
+ fps = f;
}
}
+ if (fps == 1000.0) {
+ fps = maxFps;
+ }
}
- float retFps = streamparm.parm.capture.timeperframe.denominator /
- streamparm.parm.capture.timeperframe.numerator;
- if (std::fabs(fps - retFps) > std::numeric_limits<float>::epsilon()) {
- ALOGE("%s: expect fps %f, got %f instead", __FUNCTION__, fps, retFps);
- return BAD_VALUE;
+
+ int fpsRet = setV4l2FpsLocked(fps);
+ if (fpsRet != 0 && fpsRet != -EINVAL) {
+ ALOGE("%s: set fps failed: %s", __FUNCTION__, strerror(fpsRet));
+ return fpsRet;
}
uint32_t v4lBufferCount = (fps >= kDefaultFps) ?
@@ -2136,17 +2220,8 @@
{
std::unique_lock<std::mutex> lk(mV4l2BufferLock);
if (mNumDequeuedV4l2Buffers == mV4L2BufferCount) {
- std::chrono::seconds timeout = std::chrono::seconds(kBufferWaitTimeoutSec);
- mLock.unlock();
- auto st = mV4L2BufferReturned.wait_for(lk, timeout);
- // Here we introduce a case where mV4l2BufferLock is acquired before mLock, while
- // the normal lock acquisition order is reversed, but this is fine because in most of
- // cases we are protected by mInterfaceLock. The only thread that can compete these
- // locks are the OutputThread, where we do need to make sure we don't acquire mLock then
- // mV4l2BufferLock
- mLock.lock();
- if (st == std::cv_status::timeout) {
- ALOGE("%s: wait for V4L2 buffer return timeout!", __FUNCTION__);
+ int waitRet = waitForV4L2BufferReturnLocked(lk);
+ if (waitRet != 0) {
return ret;
}
}