summaryrefslogtreecommitdiff
path: root/src/gstreamer/gstlibcamerasrc.h
blob: 0144cbc42fa626156077b2c1b2cb1791f65c4b5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2019, Collabora Ltd.
 *     Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
 *
 * gstlibcamerasrc.h - GStreamer Capture Element
 */

#ifndef __GST_LIBCAMERA_SRC_H__
#define __GST_LIBCAMERA_SRC_H__

#include <gst/gst.h>

G_BEGIN_DECLS

#define GST_TYPE_LIBCAMERA_SRC gst_libcamera_src_get_type()
G_DECLARE_FINAL_TYPE(GstLibcameraSrc, gst_libcamera_src,
		     GST_LIBCAMERA, SRC, GstElement)

G_END_DECLS

#endif /* __GST_LIBCAMERA_SRC_H__ */
href='#n60'>60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2024, Red Hat Inc.
 *
 * Exposure and gain
 */

#include "agc.h"

#include <stdint.h>

#include <libcamera/base/log.h>

namespace libcamera {

LOG_DEFINE_CATEGORY(IPASoftExposure)

namespace ipa::soft::algorithms {

/*
 * The number of bins to use for the optimal exposure calculations.
 */
static constexpr unsigned int kExposureBinsCount = 5;

/*
 * The exposure is optimal when the mean sample value of the histogram is
 * in the middle of the range.
 */
static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;

/*
 * The below value implements the hysteresis for the exposure adjustment.
 * It is small enough to have the exposure close to the optimal, and is big
 * enough to prevent the exposure from wobbling around the optimal value.
 */
static constexpr float kExposureSatisfactory = 0.2;

Agc::Agc()
{
}

void Agc::updateExposure(IPAContext &context, double exposureMSV)
{
	/*
	 * kExpDenominator of 10 gives ~10% increment/decrement;
	 * kExpDenominator of 5 - about ~20%
	 */
	static constexpr uint8_t kExpDenominator = 10;
	static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
	static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;

	double next;
	int32_t &exposure = context.activeState.agc.exposure;
	double &again = context.activeState.agc.again;

	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
		next = exposure * kExpNumeratorUp / kExpDenominator;
		if (next - exposure < 1)
			exposure += 1;
		else
			exposure = next;
		if (exposure >= context.configuration.agc.exposureMax) {
			next = again * kExpNumeratorUp / kExpDenominator;
			if (next - again < context.configuration.agc.againMinStep)
				again += context.configuration.agc.againMinStep;
			else
				again = next;
		}
	}

	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
		if (exposure == context.configuration.agc.exposureMax &&
		    again > context.configuration.agc.againMin) {
			next = again * kExpNumeratorDown / kExpDenominator;
			if (again - next < context.configuration.agc.againMinStep)
				again -= context.configuration.agc.againMinStep;
			else
				again = next;
		} else {
			next = exposure * kExpNumeratorDown / kExpDenominator;
			if (exposure - next < 1)
				exposure -= 1;
			else
				exposure = next;
		}
	}

	exposure = std::clamp(exposure, context.configuration.agc.exposureMin,
			      context.configuration.agc.exposureMax);
	again = std::clamp(again, context.configuration.agc.againMin,
			   context.configuration.agc.againMax);

	LOG(IPASoftExposure, Debug)
		<< "exposureMSV " << exposureMSV
		<< " exp " << exposure << " again " << again;
}

void Agc::process(IPAContext &context,
		  [[maybe_unused]] const uint32_t frame,
		  [[maybe_unused]] IPAFrameContext &frameContext,
		  const SwIspStats *stats,
		  [[maybe_unused]] ControlList &metadata)
{
	/*
	 * Calculate Mean Sample Value (MSV) according to formula from:
	 * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
	 */
	const auto &histogram = stats->yHistogram;
	const unsigned int blackLevelHistIdx =
		context.activeState.blc.level / (256 / SwIspStats::kYHistogramSize);
	const unsigned int histogramSize =
		SwIspStats::kYHistogramSize - blackLevelHistIdx;
	const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
	const unsigned int yHistValsPerBinMod =
		histogramSize / (histogramSize % kExposureBinsCount + 1);
	int exposureBins[kExposureBinsCount] = {};
	unsigned int denom = 0;
	unsigned int num = 0;

	for (unsigned int i = 0; i < histogramSize; i++) {
		unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
		exposureBins[idx] += histogram[blackLevelHistIdx + i];
	}

	for (unsigned int i = 0; i < kExposureBinsCount; i++) {
		LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i];
		denom += exposureBins[i];
		num += exposureBins[i] * (i + 1);
	}

	float exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom);
	updateExposure(context, exposureMSV);
}

REGISTER_IPA_ALGORITHM(Agc, "Agc")

} /* namespace ipa::soft::algorithms */

} /* namespace libcamera */