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
This commit is contained in:
Yin-Chia Yeh 2018-02-23 17:21:51 -08:00
parent 58dd1650c7
commit 3aa9ae979c
5 changed files with 269 additions and 134 deletions

View file

@ -107,7 +107,6 @@ Return<void> CameraDeviceSession::configureStreams_3_4(
const camera_metadata_t *paramBuffer = nullptr;
if (0 < requestedConfiguration.sessionParams.size()) {
::android::hardware::camera::common::V1_0::helper::CameraMetadata sessionParams;
V3_2::implementation::convertFromHidl(requestedConfiguration.sessionParams, &paramBuffer);
}

View file

@ -638,12 +638,6 @@ status_t ExternalCameraDevice::initOutputCharsKeys(int fd,
}
}
// The document in aeAvailableTargetFpsRanges section says the minFps should
// not be larger than 15.
// We cannot support fixed 30fps but Android requires (min, max) and
// (max, max) ranges.
// TODO: populate more, right now this does not support 30,30 if the device
// has higher than 30 fps modes
std::vector<int32_t> fpsRanges;
// Variable range
fpsRanges.push_back(minFps);
@ -693,7 +687,7 @@ status_t ExternalCameraDevice::initOutputCharsKeys(int fd,
#undef UPDATE
void ExternalCameraDevice::getFrameRateList(
int fd, float fpsUpperBound, SupportedV4L2Format* format) {
int fd, double fpsUpperBound, SupportedV4L2Format* format) {
format->frameRates.clear();
v4l2_frmivalenum frameInterval {
@ -715,7 +709,7 @@ void ExternalCameraDevice::getFrameRateList(
if (framerate > fpsUpperBound) {
continue;
}
ALOGI("index:%d, format:%c%c%c%c, w %d, h %d, framerate %f",
ALOGV("index:%d, format:%c%c%c%c, w %d, h %d, framerate %f",
frameInterval.index,
frameInterval.pixel_format & 0xFF,
(frameInterval.pixel_format >> 8) & 0xFF,
@ -738,71 +732,68 @@ void ExternalCameraDevice::getFrameRateList(
}
}
CroppingType ExternalCameraDevice::initCroppingType(
/*inout*/std::vector<SupportedV4L2Format>* pSortedFmts) {
std::vector<SupportedV4L2Format>& sortedFmts = *pSortedFmts;
void ExternalCameraDevice::trimSupportedFormats(
CroppingType cropType,
/*inout*/std::vector<SupportedV4L2Format>* pFmts) {
std::vector<SupportedV4L2Format>& sortedFmts = *pFmts;
if (cropType == VERTICAL) {
std::sort(sortedFmts.begin(), sortedFmts.end(),
[](const SupportedV4L2Format& a, const SupportedV4L2Format& b) -> bool {
if (a.width == b.width) {
return a.height < b.height;
}
return a.width < b.width;
});
} else {
std::sort(sortedFmts.begin(), sortedFmts.end(),
[](const SupportedV4L2Format& a, const SupportedV4L2Format& b) -> bool {
if (a.height == b.height) {
return a.width < b.width;
}
return a.height < b.height;
});
}
if (sortedFmts.size() == 0) {
ALOGE("%s: input format list is empty!", __FUNCTION__);
return;
}
const auto& maxSize = sortedFmts[sortedFmts.size() - 1];
float maxSizeAr = ASPECT_RATIO(maxSize);
float minAr = kMaxAspectRatio;
float maxAr = kMinAspectRatio;
// Remove formats that has aspect ratio not croppable from largest size
std::vector<SupportedV4L2Format> out;
for (const auto& fmt : sortedFmts) {
float ar = ASPECT_RATIO(fmt);
if (ar < minAr) {
minAr = ar;
}
if (ar > maxAr) {
maxAr = ar;
}
}
CroppingType ct = VERTICAL;
if (isAspectRatioClose(maxSizeAr, maxAr)) {
// Ex: 16:9 sensor, cropping horizontally to get to 4:3
ct = HORIZONTAL;
} else if (isAspectRatioClose(maxSizeAr, minAr)) {
// Ex: 4:3 sensor, cropping vertically to get to 16:9
ct = VERTICAL;
} else {
ALOGI("%s: camera maxSizeAr %f is not close to minAr %f or maxAr %f",
__FUNCTION__, maxSizeAr, minAr, maxAr);
if ((maxSizeAr - minAr) < (maxAr - maxSizeAr)) {
ct = VERTICAL;
if (isAspectRatioClose(ar, maxSizeAr)) {
out.push_back(fmt);
} else if (cropType == HORIZONTAL && ar < maxSizeAr) {
out.push_back(fmt);
} else if (cropType == VERTICAL && ar > maxSizeAr) {
out.push_back(fmt);
} else {
ct = HORIZONTAL;
ALOGV("%s: size (%d,%d) is removed due to unable to crop %s from (%d,%d)",
__FUNCTION__, fmt.width, fmt.height,
cropType == VERTICAL ? "vertically" : "horizontally",
maxSize.width, maxSize.height);
}
// Remove formats that has aspect ratio not croppable from largest size
std::vector<SupportedV4L2Format> out;
for (const auto& fmt : sortedFmts) {
float ar = ASPECT_RATIO(fmt);
if (isAspectRatioClose(ar, maxSizeAr)) {
out.push_back(fmt);
} else if (ct == HORIZONTAL && ar < maxSizeAr) {
out.push_back(fmt);
} else if (ct == VERTICAL && ar > maxSizeAr) {
out.push_back(fmt);
} else {
ALOGD("%s: size (%d,%d) is removed due to unable to crop %s from (%d,%d)",
__FUNCTION__, fmt.width, fmt.height,
ct == VERTICAL ? "vertically" : "horizontally",
maxSize.width, maxSize.height);
}
}
sortedFmts = out;
}
ALOGI("%s: camera croppingType is %s", __FUNCTION__,
ct == VERTICAL ? "VERTICAL" : "HORIZONTAL");
return ct;
sortedFmts = out;
}
void ExternalCameraDevice::initSupportedFormatsLocked(int fd) {
std::vector<SupportedV4L2Format>
ExternalCameraDevice::getCandidateSupportedFormatsLocked(
int fd, CroppingType cropType,
const std::vector<ExternalCameraConfig::FpsLimitation>& fpsLimits) {
std::vector<SupportedV4L2Format> outFmts;
struct v4l2_fmtdesc fmtdesc {
.index = 0,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE};
int ret = 0;
while (ret == 0) {
ret = TEMP_FAILURE_RETRY(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc));
ALOGD("index:%d,ret:%d, format:%c%c%c%c", fmtdesc.index, ret,
ALOGV("index:%d,ret:%d, format:%c%c%c%c", fmtdesc.index, ret,
fmtdesc.pixelformat & 0xFF,
(fmtdesc.pixelformat >> 8) & 0xFF,
(fmtdesc.pixelformat >> 16) & 0xFF,
@ -835,13 +826,20 @@ void ExternalCameraDevice::initSupportedFormatsLocked(int fd) {
.fourcc = fmtdesc.pixelformat
};
float fpsUpperBound = -1.0;
for (const auto& limit : mCfg.fpsLimits) {
if (format.width <= limit.size.width &&
format.height <= limit.size.height) {
fpsUpperBound = limit.fpsUpperBound;
break;
double fpsUpperBound = -1.0;
for (const auto& limit : fpsLimits) {
if (cropType == VERTICAL) {
if (format.width <= limit.size.width) {
fpsUpperBound = limit.fpsUpperBound;
break;
}
} else { // HORIZONTAL
if (format.height <= limit.size.height) {
fpsUpperBound = limit.fpsUpperBound;
break;
}
}
}
if (fpsUpperBound < 0.f) {
continue;
@ -849,7 +847,7 @@ void ExternalCameraDevice::initSupportedFormatsLocked(int fd) {
getFrameRateList(fd, fpsUpperBound, &format);
if (!format.frameRates.empty()) {
mSupportedFormats.push_back(format);
outFmts.push_back(format);
}
}
}
@ -857,16 +855,66 @@ void ExternalCameraDevice::initSupportedFormatsLocked(int fd) {
}
fmtdesc.index++;
}
trimSupportedFormats(cropType, &outFmts);
return outFmts;
}
std::sort(mSupportedFormats.begin(), mSupportedFormats.end(),
[](const SupportedV4L2Format& a, const SupportedV4L2Format& b) -> bool {
if (a.width == b.width) {
return a.height < b.height;
}
return a.width < b.width;
});
void ExternalCameraDevice::initSupportedFormatsLocked(int fd) {
mCroppingType = initCroppingType(&mSupportedFormats);
std::vector<SupportedV4L2Format> horizontalFmts =
getCandidateSupportedFormatsLocked(fd, HORIZONTAL, mCfg.fpsLimits);
std::vector<SupportedV4L2Format> verticalFmts =
getCandidateSupportedFormatsLocked(fd, VERTICAL, mCfg.fpsLimits);
size_t horiSize = horizontalFmts.size();
size_t vertSize = verticalFmts.size();
if (horiSize == 0 && vertSize == 0) {
ALOGE("%s: cannot find suitable cropping type!", __FUNCTION__);
return;
}
if (horiSize == 0) {
mSupportedFormats = verticalFmts;
mCroppingType = VERTICAL;
return;
} else if (vertSize == 0) {
mSupportedFormats = horizontalFmts;
mCroppingType = HORIZONTAL;
return;
}
const auto& maxHoriSize = horizontalFmts[horizontalFmts.size() - 1];
const auto& maxVertSize = verticalFmts[verticalFmts.size() - 1];
// Try to keep largest possible output size
// When they are the same or ambiguous, pick the one support more sizes
if (maxHoriSize.width == maxVertSize.width &&
maxHoriSize.height == maxVertSize.height) {
if (horiSize > vertSize) {
mSupportedFormats = horizontalFmts;
mCroppingType = HORIZONTAL;
} else {
mSupportedFormats = verticalFmts;
mCroppingType = VERTICAL;
}
} else if (maxHoriSize.width >= maxVertSize.width &&
maxHoriSize.height >= maxVertSize.height) {
mSupportedFormats = horizontalFmts;
mCroppingType = HORIZONTAL;
} else if (maxHoriSize.width <= maxVertSize.width &&
maxHoriSize.height <= maxVertSize.height) {
mSupportedFormats = verticalFmts;
mCroppingType = VERTICAL;
} else {
if (horiSize > vertSize) {
mSupportedFormats = horizontalFmts;
mCroppingType = HORIZONTAL;
} else {
mSupportedFormats = verticalFmts;
mCroppingType = VERTICAL;
}
}
}
} // namespace implementation

View file

@ -160,7 +160,6 @@ void ExternalCameraDeviceSession::dumpState(const native_handle_t* handle) {
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 @@ void ExternalCameraDeviceSession::dumpState(const native_handle_t* handle) {
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 @@ Return<void> ExternalCameraDeviceSession::configureStreams_3_4(
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 @@ void ExternalCameraDeviceSession::cleanupInflightFences(
}
}
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 @@ Status ExternalCameraDeviceSession::processOneCaptureRequest(const CaptureReques
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 @@ int ExternalCameraDeviceSession::v4l2StreamOffLocked() {
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 @@ int ExternalCameraDeviceSession::configureV4l2StreamLocked(const SupportedV4L2Fo
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 @@ sp<V4L2Frame> ExternalCameraDeviceSession::dequeueV4l2FrameLocked(/*out*/nsecs_t
{
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;
}
}

View file

@ -177,8 +177,11 @@ protected:
status_t initDefaultRequests();
status_t fillCaptureResult(common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp);
Status configureStreams(const V3_2::StreamConfiguration&, V3_3::HalStreamConfiguration* out);
int configureV4l2StreamLocked(const SupportedV4L2Format& fmt);
// fps = 0.0 means default, which is
// slowest fps that is at least 30, or fastest fps if 30 is not supported
int configureV4l2StreamLocked(const SupportedV4L2Format& fmt, double fps = 0.0);
int v4l2StreamOffLocked();
int setV4l2FpsLocked(double fps);
// TODO: change to unique_ptr for better tracking
sp<V4L2Frame> dequeueV4l2FrameLocked(/*out*/nsecs_t* shutterTs); // Called with mLock hold
@ -212,6 +215,8 @@ protected:
ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const;
int waitForV4L2BufferReturnLocked(std::unique_lock<std::mutex>& lk);
class OutputThread : public android::Thread {
public:
OutputThread(wp<ExternalCameraDeviceSession> parent, CroppingType);
@ -307,6 +312,7 @@ protected:
bool mV4l2Streaming = false;
SupportedV4L2Format mV4l2StreamingFmt;
double mV4l2StreamingFps = 0.0;
size_t mV4L2BufferCount = 0;
static const int kBufferWaitTimeoutSec = 3; // TODO: handle long exposure (or not allowing)

View file

@ -78,7 +78,6 @@ struct ExternalCameraDevice : public ICameraDevice {
/* End of Methods from ::android::hardware::camera::device::V3_2::ICameraDevice */
protected:
void getFrameRateList(int fd, float fpsUpperBound, SupportedV4L2Format* format);
// Init supported w/h/format/fps in mSupportedFormats. Caller still owns fd
void initSupportedFormatsLocked(int fd);
@ -92,7 +91,15 @@ protected:
status_t initOutputCharsKeys(int fd,
::android::hardware::camera::common::V1_0::helper::CameraMetadata*);
static CroppingType initCroppingType(/*inout*/std::vector<SupportedV4L2Format>*);
static void getFrameRateList(int fd, double fpsUpperBound, SupportedV4L2Format* format);
// Get candidate supported formats list of input cropping type.
static std::vector<SupportedV4L2Format> getCandidateSupportedFormatsLocked(
int fd, CroppingType cropType,
const std::vector<ExternalCameraConfig::FpsLimitation>& fpsLimits);
// Trim supported format list by the cropping type. Also sort output formats by width/height
static void trimSupportedFormats(CroppingType cropType,
/*inout*/std::vector<SupportedV4L2Format>* pFmts);
Mutex mLock;
bool mInitFailed = false;