Camera: various external camera CTS fixes
1. Update the FPS range to list (0.5*fps, fps) only
as webcams tends to skip a lot of frames and not
able to output at stable framerate.
2. Exif: don't expect focal length to present
3. Thumbnail: allow 0x0 size for no thumbnail output
4. Allow retry some ioctl during configureStream as
some webcams seems having problem in quick close
reopen operation.
Test: CTS CameraTest
Bug: 72261912
Change-Id: Ic23b7fb293b7579694c59240e854d750c842886d
diff --git a/camera/common/1.0/default/Exif.cpp b/camera/common/1.0/default/Exif.cpp
index 3e894f9..6054999 100644
--- a/camera/common/1.0/default/Exif.cpp
+++ b/camera/common/1.0/default/Exif.cpp
@@ -983,15 +983,15 @@
camera_metadata_ro_entry entry = metadata.find(ANDROID_LENS_FOCAL_LENGTH);
if (entry.count) {
focal_length = entry.data.f[0];
+
+ if (!setFocalLength(
+ static_cast<uint32_t>(focal_length * kRationalPrecision),
+ kRationalPrecision)) {
+ ALOGE("%s: setting focal length failed.", __FUNCTION__);
+ return false;
+ }
} else {
- ALOGE("%s: Cannot find focal length in metadata.", __FUNCTION__);
- return false;
- }
- if (!setFocalLength(
- static_cast<uint32_t>(focal_length * kRationalPrecision),
- kRationalPrecision)) {
- ALOGE("%s: setting focal length failed.", __FUNCTION__);
- return false;
+ ALOGV("%s: Cannot find focal length in metadata.", __FUNCTION__);
}
if (metadata.exists(ANDROID_JPEG_GPS_COORDINATES)) {
diff --git a/camera/device/3.4/default/ExternalCameraDevice.cpp b/camera/device/3.4/default/ExternalCameraDevice.cpp
index 9a02ce8..037cef8 100644
--- a/camera/device/3.4/default/ExternalCameraDevice.cpp
+++ b/camera/device/3.4/default/ExternalCameraDevice.cpp
@@ -639,14 +639,14 @@
}
std::vector<int32_t> fpsRanges;
- // Variable range
- fpsRanges.push_back(minFps);
- fpsRanges.push_back(maxFps);
- // Fixed ranges
+ // FPS ranges
for (const auto& framerate : framerates) {
- fpsRanges.push_back(framerate);
+ // Empirical: webcams often have close to 2x fps error and cannot support fixed fps range
+ fpsRanges.push_back(framerate / 2);
fpsRanges.push_back(framerate);
}
+ maxFrameDuration *= 2;
+
UPDATE(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, fpsRanges.data(),
fpsRanges.size());
diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
index 5569439..29aa1c3 100644
--- a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
+++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp
@@ -47,6 +47,9 @@
const int kBadFramesAfterStreamOn = 1; // drop x frames after streamOn to get rid of some initial
// bad frames. TODO: develop a better bad frame detection
// method
+constexpr int MAX_RETRY = 15; // Allow retry some ioctl failures a few times to account for some
+ // webcam showing temporarily ioctl failures.
+constexpr int IOCTL_RETRY_SLEEP_US = 33000; // 33ms * MAX_RETRY = 5 seconds
bool tryLock(Mutex& mutex)
{
@@ -1480,6 +1483,7 @@
int jpegQuality, thumbQuality;
Size thumbSize;
+ bool outputThumbnail = true;
if (req->setting.exists(ANDROID_JPEG_QUALITY)) {
camera_metadata_entry entry =
@@ -1505,6 +1509,9 @@
thumbSize = Size { static_cast<uint32_t>(entry.data.i32[0]),
static_cast<uint32_t>(entry.data.i32[1])
};
+ if (thumbSize.width == 0 && thumbSize.height == 0) {
+ outputThumbnail = false;
+ }
} else {
return lfail(
"%s: ANDROID_JPEG_THUMBNAIL_SIZE not set", __FUNCTION__);
@@ -1532,14 +1539,16 @@
/* Hold actual thumbnail and main image code sizes */
size_t thumbCodeSize = 0, jpegCodeSize = 0;
/* Temporary thumbnail code buffer */
- std::vector<uint8_t> thumbCode(maxThumbCodeSize);
+ std::vector<uint8_t> thumbCode(outputThumbnail ? maxThumbCodeSize : 0);
YCbCrLayout yu12Thumb;
- ret = cropAndScaleThumbLocked(mYu12Frame, thumbSize, &yu12Thumb);
+ if (outputThumbnail) {
+ ret = cropAndScaleThumbLocked(mYu12Frame, thumbSize, &yu12Thumb);
- if (ret != 0) {
- return lfail(
- "%s: crop and scale thumbnail failed!", __FUNCTION__);
+ if (ret != 0) {
+ return lfail(
+ "%s: crop and scale thumbnail failed!", __FUNCTION__);
+ }
}
/* Scale and crop main jpeg */
@@ -1550,12 +1559,14 @@
}
/* Encode the thumbnail image */
- ret = encodeJpegYU12(thumbSize, yu12Thumb,
- thumbQuality, 0, 0,
- &thumbCode[0], maxThumbCodeSize, thumbCodeSize);
+ if (outputThumbnail) {
+ ret = encodeJpegYU12(thumbSize, yu12Thumb,
+ thumbQuality, 0, 0,
+ &thumbCode[0], maxThumbCodeSize, thumbCodeSize);
- if (ret != 0) {
- return lfail("%s: encodeJpegYU12 failed with %d",__FUNCTION__, ret);
+ if (ret != 0) {
+ return lfail("%s: thumbnail encodeJpegYU12 failed with %d",__FUNCTION__, ret);
+ }
}
/* Combine camera characteristics with request settings to form EXIF
@@ -1570,11 +1581,7 @@
utils->setFromMetadata(meta, jpegSize.width, jpegSize.height);
- /* Check if we made a non-zero-sized thumbnail. Currently not possible
- * that we got this far and the code is size 0, but if this code moves
- * around it might become relevant again */
-
- ret = utils->generateApp1(thumbCodeSize ? &thumbCode[0] : 0, thumbCodeSize);
+ ret = utils->generateApp1(outputThumbnail ? &thumbCode[0] : 0, thumbCodeSize);
if (!ret) {
return lfail("%s: generating APP1 failed", __FUNCTION__);
@@ -2109,8 +2116,20 @@
fmt.fmt.pix.pixelformat = v4l2Fmt.fourcc;
ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_FMT, &fmt));
if (ret < 0) {
- ALOGE("%s: S_FMT ioctl failed: %s", __FUNCTION__, strerror(errno));
- return -errno;
+ int numAttempt = 0;
+ while (ret < 0) {
+ ALOGW("%s: VIDIOC_S_FMT failed, wait 33ms and try again", __FUNCTION__);
+ usleep(IOCTL_RETRY_SLEEP_US); // sleep 100 ms and try again
+ ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_FMT, &fmt));
+ if (numAttempt == MAX_RETRY) {
+ break;
+ }
+ numAttempt++;
+ }
+ if (ret < 0) {
+ ALOGE("%s: S_FMT ioctl failed: %s", __FUNCTION__, strerror(errno));
+ return -errno;
+ }
}
if (v4l2Fmt.width != fmt.fmt.pix.width || v4l2Fmt.height != fmt.fmt.pix.height ||
@@ -2199,9 +2218,22 @@
// VIDIOC_STREAMON: start streaming
v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMON, &capture_type)) < 0) {
- ALOGE("%s: VIDIOC_STREAMON failed: %s", __FUNCTION__, strerror(errno));
- return -errno;
+ ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMON, &capture_type));
+ if (ret < 0) {
+ int numAttempt = 0;
+ while (ret < 0) {
+ ALOGW("%s: VIDIOC_STREAMON failed, wait 33ms and try again", __FUNCTION__);
+ usleep(IOCTL_RETRY_SLEEP_US); // sleep 100 ms and try again
+ ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_STREAMON, &capture_type));
+ if (numAttempt == MAX_RETRY) {
+ break;
+ }
+ numAttempt++;
+ }
+ if (ret < 0) {
+ ALOGE("%s: VIDIOC_STREAMON ioctl failed: %s", __FUNCTION__, strerror(errno));
+ return -errno;
+ }
}
// Swallow first few frames after streamOn to account for bad frames from some devices
@@ -2220,6 +2252,8 @@
}
}
+ ALOGI("%s: start V4L2 streaming %dx%d@%ffps",
+ __FUNCTION__, v4l2Fmt.width, v4l2Fmt.height, fps);
mV4l2StreamingFmt = v4l2Fmt;
mV4l2Streaming = true;
return OK;