Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8b699aaf authored by Yin-Chia Yeh's avatar Yin-Chia Yeh
Browse files

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
parent c2b7f42d
Loading
Loading
Loading
Loading
+8 −8
Original line number Diff line number Diff line
@@ -983,16 +983,16 @@ bool ExifUtilsImpl::setFromMetadata(const CameraMetadata& metadata,
    camera_metadata_ro_entry entry = metadata.find(ANDROID_LENS_FOCAL_LENGTH);
    if (entry.count) {
        focal_length = entry.data.f[0];
    } 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;
        }
    } else {
        ALOGV("%s: Cannot find focal length in metadata.", __FUNCTION__);
    }

    if (metadata.exists(ANDROID_JPEG_GPS_COORDINATES)) {
        entry = metadata.find(ANDROID_JPEG_GPS_COORDINATES);
+5 −5
Original line number Diff line number Diff line
@@ -639,14 +639,14 @@ status_t ExternalCameraDevice::initOutputCharsKeys(int fd,
    }

    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());

+54 −20
Original line number Diff line number Diff line
@@ -47,6 +47,9 @@ static constexpr size_t kMetadataMsgQueueSize = 1 << 18 /* 256kB */;
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 ExternalCameraDeviceSession::OutputThread::createJpegLocked(

    int jpegQuality, thumbQuality;
    Size thumbSize;
    bool outputThumbnail = true;

    if (req->setting.exists(ANDROID_JPEG_QUALITY)) {
        camera_metadata_entry entry =
@@ -1505,6 +1509,9 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked(
        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,15 +1539,17 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked(
    /* 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;
    if (outputThumbnail) {
        ret = cropAndScaleThumbLocked(mYu12Frame, thumbSize, &yu12Thumb);

        if (ret != 0) {
            return lfail(
                "%s: crop and scale thumbnail failed!", __FUNCTION__);
        }
    }

    /* Scale and crop main jpeg */
    ret = cropAndScaleLocked(mYu12Frame, jpegSize, &yu12Main);
@@ -1550,12 +1559,14 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked(
    }

    /* Encode the thumbnail image */
    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);
            return lfail("%s: thumbnail encodeJpegYU12 failed with %d",__FUNCTION__, ret);
        }
    }

    /* Combine camera characteristics with request settings to form EXIF
@@ -1570,11 +1581,7 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked(

    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__);
@@ -2108,10 +2115,22 @@ int ExternalCameraDeviceSession::configureV4l2StreamLocked(
    fmt.fmt.pix.height = v4l2Fmt.height;
    fmt.fmt.pix.pixelformat = v4l2Fmt.fourcc;
    ret = TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_S_FMT, &fmt));
    if (ret < 0) {
        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 ||
            v4l2Fmt.fourcc != fmt.fmt.pix.pixelformat) {
@@ -2199,10 +2218,23 @@ int ExternalCameraDeviceSession::configureV4l2StreamLocked(

    // 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));
    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
    for (int i = 0; i < kBadFramesAfterStreamOn; i++) {
@@ -2220,6 +2252,8 @@ int ExternalCameraDeviceSession::configureV4l2StreamLocked(
        }
    }

    ALOGI("%s: start V4L2 streaming %dx%d@%ffps",
                __FUNCTION__, v4l2Fmt.width, v4l2Fmt.height, fps);
    mV4l2StreamingFmt = v4l2Fmt;
    mV4l2Streaming = true;
    return OK;