fp6-img/aports/temp/libcamera/0010-af-two-phase-sweep-over-a-brightness-normalised-cent.patch
Jorijn van der Graaf 40ccdfeed6
Some checks failed
image / image (push) Has been cancelled
libcamera: backport contrast autofocus for the main camera (r4)
The DW9784 VCM works and libcamera discovers it through the sensor's
ancillary link, but nothing in the software ISP ever moves it. Backport
the out-of-tree autofocus work onto v0.7.2:

- 0007-0009 are Vasiliy Doylov's focus control, contrast autofocus and
  focus-loss detection from the softisp-playground branch;
- 0010 is Pavel Machek's Librem5-tested robustness work (centre-window,
  brightness-normalised sharpness, two-phase sweep, settle skip),
  squashed and adapted;
- 0011-0013 are ours: the lens write no longer sits inside the
  no-frame-start-emitter branch, the lensless-camera paths are guarded,
  and AfMode is advertised with a continuous default so that stock
  applications get autofocus without sending AfTrigger.

Provenance and the adaptations made to each patch are recorded in the
patch commit messages.
2026-08-17 02:02:45 +02:00

529 lines
20 KiB
Diff

From eeaef1aff4f95ca78f6005b13cf82a9bccee5844 Mon Sep 17 00:00:00 2001
From: Pavel Machek <pavel@ucw.cz>
Date: Mon, 17 Aug 2026 01:57:53 +0200
Subject: [PATCH] af: two-phase sweep over a brightness-normalised
centre-window metric
Three changes that together turn the sweep from "moves the lens" into
"finds the focus":
- Measure sharpness over the centre of the frame only, so that a
cluttered background does not outvote the subject.
- Compare green samples two sampling steps apart instead of taking a
full RGB Laplacian, which keeps the metric away from the spatial
frequencies where sensor noise lives, and normalise the result by the
brightness of the same pixels: the AGC keeps moving while a sweep
runs, and an unnormalised metric simply follows the exposure.
- Sweep in two phases (coarse, then fine around the coarse maximum) and
skip a few frames after every large lens movement so that the lens has
settled before the next sample is taken.
[Squashed from da42da564afa, 3ac05187459d and 4312a90e32ea on
gitlab.com/tui/libcamera millicam_af_6, and adapted to libcamera
v0.7.2:
- the statistics line functions take a SwIspStats reference for
multi-threaded stats, so the y coordinate is threaded through as an
extra parameter rather than replacing one;
- the centre window is band 2 of 5 on both axes (the original used band
3, which is off-centre), and the x window is derived from each
format's own loop bound because the packed formats count bytes, not
pixels;
- the normalisation divides by the square of the mean green level of
the sampled pixels, which makes the metric independent of exposure
rather than merely less dependent on it, and is guarded against an
all-black window;
- the sweep constants are named and tuned for a 0..4095 VCM sampled
once every four frames: coarse 10%, fine 2%, ending below 1%;
- the mcam test application, the forced-focus button and the
AeState/step debug output are dropped.]
Signed-off-by: Pavel Machek <pavel@ucw.cz>
---
.../internal/software_isp/swstats_cpu.h | 20 +--
src/ipa/simple/algorithms/af.cpp | 138 +++++++++++-------
src/ipa/simple/algorithms/af.h | 5 +-
src/ipa/simple/ipa_context.h | 2 +
src/libcamera/software_isp/swstats_cpu.cpp | 84 +++++++----
5 files changed, 160 insertions(+), 89 deletions(-)
diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
index 551870921..df399d71c 100644
--- a/include/libcamera/internal/software_isp/swstats_cpu.h
+++ b/include/libcamera/internal/software_isp/swstats_cpu.h
@@ -67,7 +67,7 @@ public:
y >= (window_.y + window_.height))
return;
- (this->*stats0_)(src, stats_[statsBufferIndex]);
+ (this->*stats0_)(src, y, stats_[statsBufferIndex]);
}
void processLine2(uint32_t frame, unsigned int y, const uint8_t *src[], unsigned int statsBufferIndex = 0)
@@ -79,28 +79,28 @@ public:
y >= (window_.y + window_.height))
return;
- (this->*stats2_)(src, stats_[statsBufferIndex]);
+ (this->*stats2_)(src, y, stats_[statsBufferIndex]);
}
Signal<uint32_t, uint32_t> statsReady;
private:
- using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[], SwIspStats &stats);
+ using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[], unsigned int y, SwIspStats &stats);
using processFrameFn = void (SwStatsCpu::*)(MappedFrameBuffer &in);
int setupStandardBayerOrder(BayerFormat::Order order);
/* Bayer 8 bpp unpacked */
- void statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats);
+ void statsBGGR8Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats);
/* Bayer 10 bpp unpacked */
- void statsBGGR10Line0(const uint8_t *src[], SwIspStats &stats);
+ void statsBGGR10Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats);
/* Bayer 12 bpp unpacked */
- void statsBGGR12Line0(const uint8_t *src[], SwIspStats &stats);
+ void statsBGGR12Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats);
/* Bayer 10 bpp packed */
- void statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats);
- void statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats);
+ void statsBGGR10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats);
+ void statsGBRG10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats);
/* Bayer 12 bpp packed */
- void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats);
- void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats);
+ void statsBGGR12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats);
+ void statsGBRG12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats);
void processBayerFrame2(MappedFrameBuffer &in);
diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp
index f321f361e..19235a567 100644
--- a/src/ipa/simple/algorithms/af.cpp
+++ b/src/ipa/simple/algorithms/af.cpp
@@ -7,6 +7,7 @@
#include "af.h"
+#include <algorithm>
#include <stdint.h>
#include <libcamera/base/log.h>
@@ -20,7 +21,23 @@ LOG_DEFINE_CATEGORY(IPASoftAutoFocus)
namespace ipa::soft::algorithms {
+namespace {
+
+/* Percentage of the lens travel between two samples of the coarse sweep. */
+constexpr double kCoarseStep = 10.0;
+/* The step is divided by this at every phase of the sweep. */
+constexpr double kStepDivisor = 5.0;
+/* The sweep ends once the step would become finer than this. */
+constexpr double kFineStepMin = 1.0;
+/* Stats frames to ignore after a large lens movement. */
+constexpr uint32_t kSettleSkipLong = 3;
+/* Relative sharpness change that makes a settled scene worth re-focusing. */
+constexpr double kFocusLossThreshold = 0.3;
+
+} /* namespace */
+
Af::Af()
+ : steps_(0)
{
}
@@ -39,6 +56,7 @@ int Af::configure(IPAContext &context,
context.activeState.knobs.focus_pos = std::optional<double>();
context.activeState.knobs.focus_sweep = false;
context.activeState.knobs.focus_pos = 0;
+ context.configuration.focus.skip = kSettleSkipLong;
return 0;
}
@@ -55,17 +73,9 @@ void Af::queueRequest([[maybe_unused]] typename Module::Context &context,
}
if (af_trigger.has_value()) {
context.activeState.knobs.focus_sweep = af_trigger.value() == 1;
- if(context.activeState.knobs.focus_sweep){
- context.activeState.knobs.focus_pos = 0;
- context.configuration.focus.focus_max_pos = 0;
- context.configuration.focus.sharpness_max = 0;
- context.configuration.focus.start = 0;
- context.configuration.focus.stop = 100;
- context.configuration.focus.step = 25;
- LOG(IPASoftAutoFocus, Info) << "Starting focus sweep";
- }
+ if (context.activeState.knobs.focus_sweep)
+ restart(context);
}
-
}
void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, [[maybe_unused]] double exposureMSV)
@@ -73,45 +83,78 @@ void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAF
frameContext.lens.focus_pos = context.activeState.knobs.focus_pos.value_or(50.0) / 100.0 * (context.configuration.focus.focus_max - context.configuration.focus.focus_min);
}
-void Af::step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep){
- if(!sweep){
- if(utils::abs_diff(sharp, max_sharp) > max_sharp*0.3){
- LOG(IPASoftAutoFocus, Info) << "Focus lost :(";
- sweep = true;
- max_sharp = 0;
- max_pos = 0;
- focus_pos = 0;
- start = 0;
- stop = 100;
- step = 25;
- }
- else {
- return;
- }
+void Af::restart(IPAContext &context)
+{
+ auto &focus = context.configuration.focus;
+
+ steps_ = 0;
+ focus.focus_max_pos = 0;
+ focus.sharpness_max = 0;
+ focus.start = 0;
+ focus.stop = 100;
+ focus.step = kCoarseStep;
+ focus.skip = kSettleSkipLong;
+ context.activeState.knobs.focus_pos = 0;
+ context.activeState.knobs.focus_sweep = true;
+ LOG(IPASoftAutoFocus, Info) << "Starting focus sweep";
+}
+
+void Af::step(IPAContext &context, double &focus_pos, uint64_t sharp, bool &sweep)
+{
+ auto &focus = context.configuration.focus;
+
+ steps_++;
+
+ /* Even during a sweep the lens needs time to reach the new position. */
+ if (focus.skip != 0) {
+ focus.skip--;
+ return;
}
- if(focus_pos < start) {
- focus_pos = start;
+
+ if (!sweep) {
+ if (utils::abs_diff(sharp, focus.sharpness_max) >
+ focus.sharpness_max * kFocusLossThreshold) {
+ LOG(IPASoftAutoFocus, Info)
+ << "Focus lost: " << sharp << " vs "
+ << focus.sharpness_max;
+ restart(context);
+ }
return;
}
- if(sharp > max_sharp) {
- max_sharp = sharp;
- max_pos = focus_pos;
+
+ if (sharp > focus.sharpness_max) {
+ focus.sharpness_max = sharp;
+ focus.focus_max_pos = focus_pos;
}
- if(focus_pos >= stop) {
- LOG(IPASoftAutoFocus, Info) << "Best focus on step " <<step << ": " << focus_pos;
- start = std::clamp(max_pos - step, 0.0, 100.0);
- stop = std::clamp(max_pos + step, 0.0, 100.0);
- focus_pos = start;
- step /= 2;
- if(step <= 0.2){
- sweep = false;
- LOG(IPASoftAutoFocus, Info) << "Sweep end. Best focus: " << max_pos;
- focus_pos = max_pos;
- }
+
+ focus_pos += focus.step;
+ if (focus_pos > focus.start && focus_pos < focus.stop)
+ return;
+
+ /* Sweep phase over, narrow the range around the best position. */
+ focus.start = std::clamp(focus.focus_max_pos - focus.step, 0.0, 100.0);
+ focus.stop = std::clamp(focus.focus_max_pos + focus.step, 0.0, 100.0);
+ LOG(IPASoftAutoFocus, Info)
+ << "Best focus with step " << focus.step << ": "
+ << focus.focus_max_pos << " (sharpness "
+ << focus.sharpness_max << "), next range " << focus.start
+ << ".." << focus.stop;
+ focus_pos = focus.start;
+ focus.step /= kStepDivisor;
+ focus.skip = kSettleSkipLong;
+
+ if (focus.step >= kFineStepMin) {
+ /* Look for the maximum again at the finer step. */
+ focus.sharpness_max = 0;
return;
}
- focus_pos += step;
+ sweep = false;
+ focus_pos = focus.focus_max_pos;
+ LOG(IPASoftAutoFocus, Info)
+ << "Sweep end. Best focus: " << focus.focus_max_pos
+ << " after " << steps_ << " frames";
+ /* sharpness_max is kept to detect the scene changing later on. */
}
void Af::process([[maybe_unused]] IPAContext &context,
@@ -121,14 +164,9 @@ void Af::process([[maybe_unused]] IPAContext &context,
[[maybe_unused]] ControlList &metadata)
{
if (stats->valid) {
- step(context.configuration.focus.start,
- context.configuration.focus.stop,
- context.configuration.focus.step,
- context.activeState.knobs.focus_pos.value(),
- context.configuration.focus.focus_max_pos,
- context.configuration.focus.sharpness_max,
- stats->sharpness,
- context.activeState.knobs.focus_sweep.value());
+ step(context, context.activeState.knobs.focus_pos.value(),
+ stats->sharpness,
+ context.activeState.knobs.focus_sweep.value());
}
updateFocus(context, frameContext, 0);
diff --git a/src/ipa/simple/algorithms/af.h b/src/ipa/simple/algorithms/af.h
index 4d0ea58b3..0e0f19423 100644
--- a/src/ipa/simple/algorithms/af.h
+++ b/src/ipa/simple/algorithms/af.h
@@ -33,7 +33,10 @@ public:
private:
void updateFocus(IPAContext &context, IPAFrameContext &frameContext, double focus);
- void step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep);
+ void step(IPAContext &context, double &focus_pos, uint64_t sharp, bool &sweep);
+ void restart(IPAContext &context);
+
+ unsigned int steps_;
};
} /* namespace ipa::soft::algorithms */
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index f869d1a67..4fc9c7cdf 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -38,6 +38,8 @@ struct IPASessionConfiguration {
double focus_max_pos;
uint64_t sharpness_max;
double start, stop, step;
+ /* Stats frames to ignore while the lens settles */
+ uint32_t skip;
} focus;
};
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index f243992fb..2cbdbefa3 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -130,6 +130,7 @@ namespace libcamera {
* \typedef SwStatsCpu::statsProcessFn
* \brief Called when there is data to get statistics from
* \param[in] src The input data
+ * \param[in] y The y coordinate of the line, relative to the window
*
* These functions take an array of (patternSize_.height + 1) src
* pointers each pointing to a line in the source image. The middle
@@ -171,17 +172,34 @@ static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */
static constexpr unsigned int kGreenYMul = 150; /* 0.587 * 256 */
static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */
-#define SWSTATS_START_LINE_STATS(pixel_t) \
+/*
+ * The sharpness metric is only gathered over the centre fifth of the frame,
+ * which is where the subject an autofocus run should focus on normally is.
+ * \a lineLength is the upper bound of the sampling loop of the caller, in the
+ * units that loop counts in (pixels or bytes depending on the format).
+ */
+#define SWSTATS_START_LINE_STATS(pixel_t, lineLength) \
pixel_t r, g, g2, b; \
uint64_t yVal; \
\
uint64_t sumR = 0; \
uint64_t sumG = 0; \
uint64_t sumB = 0; \
- pixel_t r0 = 0, r1 = 0, b0 = 0, \
- b1 = 0, g0 = 0, g1 = 0; \
- uint64_t sharpness = 0;
+ pixel_t gPrev = 0, gPrev2 = 0; \
+ uint64_t sharpness = 0; \
+ uint64_t sharpSumG = 0; \
+ unsigned int sharpCount = 0; \
+ const unsigned int sharpXBegin = (lineLength) * 2 / 5; \
+ const unsigned int sharpXEnd = (lineLength) * 3 / 5; \
+ const bool sharpRow = y >= window_.height * 2 / 5 && \
+ y < window_.height * 3 / 5;
+/*
+ * Sharpness is the sum of the squared differences between green samples two
+ * sampling steps apart; skipping one sample keeps the metric away from the
+ * highest spatial frequencies, where sensor noise dominates. gPrev/gPrev2 are
+ * updated for every sample so that the window always has valid history.
+ */
#define SWSTATS_ACCUMULATE_LINE_STATS(div) \
sumR += r; \
sumG += g; \
@@ -191,26 +209,36 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */
yVal += g * kGreenYMul; \
yVal += b * kBlueYMul; \
stats.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++; \
- if (r0 != 0) \
- sharpness += abs(r - 2 * r1 + r0) * kRedYMul + abs(g - 2 * g1 + g0) * kGreenYMul + abs(b - 2 * b1 + b0) * kBlueYMul; \
- r0 = r1; \
- g0 = g1; \
- b0 = b1; \
- r1 = r; \
- g1 = g; \
- b1 = b;
+ if (sharpRow && x >= sharpXBegin && x < sharpXEnd) { \
+ const int64_t gDiff = static_cast<int64_t>(g) - gPrev2; \
+ sharpness += gDiff * gDiff; \
+ sharpSumG += g; \
+ sharpCount++; \
+ } \
+ gPrev2 = gPrev; \
+ gPrev = g;
+
+/*
+ * Normalise the sharpness by the square of the mean green level of the same
+ * pixels, so that the metric tracks contrast rather than exposure: the AGC
+ * keeps moving while an autofocus sweep runs.
+ */
#define SWSTATS_FINISH_LINE_STATS() \
stats.sum_.r() += sumR; \
stats.sum_.g() += sumG; \
stats.sum_.b() += sumB; \
- stats.sharpness += sharpness;
+ if (sharpCount) { \
+ const uint64_t meanG = sharpSumG / sharpCount; \
+ if (meanG) \
+ stats.sharpness += sharpness * 1024 / (meanG * meanG); \
+ }
-void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats)
+void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats)
{
const uint8_t *src0 = src[1] + window_.x;
const uint8_t *src1 = src[2] + window_.x;
- SWSTATS_START_LINE_STATS(uint8_t)
+ SWSTATS_START_LINE_STATS(uint8_t, window_.width)
if (swapLines_)
std::swap(src0, src1);
@@ -230,12 +258,12 @@ void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats)
SWSTATS_FINISH_LINE_STATS()
}
-void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[], SwIspStats &stats)
+void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats)
{
const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
- SWSTATS_START_LINE_STATS(uint16_t)
+ SWSTATS_START_LINE_STATS(uint16_t, window_.width)
if (swapLines_)
std::swap(src0, src1);
@@ -256,12 +284,12 @@ void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[], SwIspStats &stats)
SWSTATS_FINISH_LINE_STATS()
}
-void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[], SwIspStats &stats)
+void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats)
{
const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
- SWSTATS_START_LINE_STATS(uint16_t)
+ SWSTATS_START_LINE_STATS(uint16_t, window_.width)
if (swapLines_)
std::swap(src0, src1);
@@ -282,7 +310,7 @@ void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[], SwIspStats &stats)
SWSTATS_FINISH_LINE_STATS()
}
-void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats)
+void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats)
{
const uint8_t *src0 = src[1] + window_.x * 5 / 4;
const uint8_t *src1 = src[2] + window_.x * 5 / 4;
@@ -291,7 +319,7 @@ void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats)
if (swapLines_)
std::swap(src0, src1);
- SWSTATS_START_LINE_STATS(uint8_t)
+ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes)
/* x += 5 sample every other 2x2 block */
for (unsigned int x = 0; x < widthInBytes; x += 5) {
@@ -308,7 +336,7 @@ void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats)
SWSTATS_FINISH_LINE_STATS()
}
-void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats)
+void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats)
{
const uint8_t *src0 = src[1] + window_.x * 5 / 4;
const uint8_t *src1 = src[2] + window_.x * 5 / 4;
@@ -317,7 +345,7 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats)
if (swapLines_)
std::swap(src0, src1);
- SWSTATS_START_LINE_STATS(uint8_t)
+ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes)
/* x += 5 sample every other 2x2 block */
for (unsigned int x = 0; x < widthInBytes; x += 5) {
@@ -334,13 +362,13 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats)
SWSTATS_FINISH_LINE_STATS()
}
-void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats)
+void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats)
{
const uint8_t *src0 = src[1] + window_.x * 3 / 2;
const uint8_t *src1 = src[2] + window_.x * 3 / 2;
const unsigned int widthInBytes = window_.width * 3 / 2;
- SWSTATS_START_LINE_STATS(uint8_t)
+ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes)
if (swapLines_)
std::swap(src0, src1);
@@ -360,13 +388,13 @@ void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats)
SWSTATS_FINISH_LINE_STATS()
}
-void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats)
+void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats)
{
const uint8_t *src0 = src[1] + window_.x * 3 / 2;
const uint8_t *src1 = src[2] + window_.x * 3 / 2;
const unsigned int widthInBytes = window_.width * 3 / 2;
- SWSTATS_START_LINE_STATS(uint8_t)
+ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes)
if (swapLines_)
std::swap(src0, src1);
@@ -603,7 +631,7 @@ void SwStatsCpu::processBayerFrame2(MappedFrameBuffer &in)
/* linePointers[0] is not used by any stats0_ functions */
linePointers[1] = src;
linePointers[2] = src + stride_;
- (this->*stats0_)(linePointers, stats_[0]);
+ (this->*stats0_)(linePointers, y, stats_[0]);
src += stride_ * 2;
}
}