1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Functions for auto gain. |
4 | * |
5 | * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> |
6 | */ |
7 | #include "gspca.h" |
8 | |
9 | /* auto gain and exposure algorithm based on the knee algorithm described here: |
10 | http://ytse.tricolour.net/docs/LowLightOptimization.html |
11 | |
12 | Returns 0 if no changes were made, 1 if the gain and or exposure settings |
13 | where changed. */ |
14 | int gspca_expo_autogain( |
15 | struct gspca_dev *gspca_dev, |
16 | int avg_lum, |
17 | int desired_avg_lum, |
18 | int deadzone, |
19 | int gain_knee, |
20 | int exposure_knee) |
21 | { |
22 | s32 gain, orig_gain, exposure, orig_exposure; |
23 | int i, steps, retval = 0; |
24 | |
25 | if (v4l2_ctrl_g_ctrl(ctrl: gspca_dev->autogain) == 0) |
26 | return 0; |
27 | |
28 | orig_gain = gain = v4l2_ctrl_g_ctrl(ctrl: gspca_dev->gain); |
29 | orig_exposure = exposure = v4l2_ctrl_g_ctrl(ctrl: gspca_dev->exposure); |
30 | |
31 | /* If we are of a multiple of deadzone, do multiple steps to reach the |
32 | desired lumination fast (with the risc of a slight overshoot) */ |
33 | steps = abs(desired_avg_lum - avg_lum) / deadzone; |
34 | |
35 | gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n" , |
36 | avg_lum, desired_avg_lum, steps); |
37 | |
38 | for (i = 0; i < steps; i++) { |
39 | if (avg_lum > desired_avg_lum) { |
40 | if (gain > gain_knee) |
41 | gain--; |
42 | else if (exposure > exposure_knee) |
43 | exposure--; |
44 | else if (gain > gspca_dev->gain->default_value) |
45 | gain--; |
46 | else if (exposure > gspca_dev->exposure->minimum) |
47 | exposure--; |
48 | else if (gain > gspca_dev->gain->minimum) |
49 | gain--; |
50 | else |
51 | break; |
52 | } else { |
53 | if (gain < gspca_dev->gain->default_value) |
54 | gain++; |
55 | else if (exposure < exposure_knee) |
56 | exposure++; |
57 | else if (gain < gain_knee) |
58 | gain++; |
59 | else if (exposure < gspca_dev->exposure->maximum) |
60 | exposure++; |
61 | else if (gain < gspca_dev->gain->maximum) |
62 | gain++; |
63 | else |
64 | break; |
65 | } |
66 | } |
67 | |
68 | if (gain != orig_gain) { |
69 | v4l2_ctrl_s_ctrl(ctrl: gspca_dev->gain, val: gain); |
70 | retval = 1; |
71 | } |
72 | if (exposure != orig_exposure) { |
73 | v4l2_ctrl_s_ctrl(ctrl: gspca_dev->exposure, val: exposure); |
74 | retval = 1; |
75 | } |
76 | |
77 | if (retval) |
78 | gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n" , |
79 | gain, exposure); |
80 | return retval; |
81 | } |
82 | EXPORT_SYMBOL(gspca_expo_autogain); |
83 | |
84 | /* Autogain + exposure algorithm for cameras with a coarse exposure control |
85 | (usually this means we can only control the clockdiv to change exposure) |
86 | As changing the clockdiv so that the fps drops from 30 to 15 fps for |
87 | example, will lead to a huge exposure change (it effectively doubles), |
88 | this algorithm normally tries to only adjust the gain (between 40 and |
89 | 80 %) and if that does not help, only then changes exposure. This leads |
90 | to a much more stable image then using the knee algorithm which at |
91 | certain points of the knee graph will only try to adjust exposure, |
92 | which leads to oscillating as one exposure step is huge. |
93 | |
94 | Returns 0 if no changes were made, 1 if the gain and or exposure settings |
95 | where changed. */ |
96 | int gspca_coarse_grained_expo_autogain( |
97 | struct gspca_dev *gspca_dev, |
98 | int avg_lum, |
99 | int desired_avg_lum, |
100 | int deadzone) |
101 | { |
102 | s32 gain_low, gain_high, gain, orig_gain, exposure, orig_exposure; |
103 | int steps, retval = 0; |
104 | |
105 | if (v4l2_ctrl_g_ctrl(ctrl: gspca_dev->autogain) == 0) |
106 | return 0; |
107 | |
108 | orig_gain = gain = v4l2_ctrl_g_ctrl(ctrl: gspca_dev->gain); |
109 | orig_exposure = exposure = v4l2_ctrl_g_ctrl(ctrl: gspca_dev->exposure); |
110 | |
111 | gain_low = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / |
112 | 5 * 2 + gspca_dev->gain->minimum; |
113 | gain_high = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) / |
114 | 5 * 4 + gspca_dev->gain->minimum; |
115 | |
116 | /* If we are of a multiple of deadzone, do multiple steps to reach the |
117 | desired lumination fast (with the risc of a slight overshoot) */ |
118 | steps = (desired_avg_lum - avg_lum) / deadzone; |
119 | |
120 | gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n" , |
121 | avg_lum, desired_avg_lum, steps); |
122 | |
123 | if ((gain + steps) > gain_high && |
124 | exposure < gspca_dev->exposure->maximum) { |
125 | gain = gain_high; |
126 | gspca_dev->exp_too_low_cnt++; |
127 | gspca_dev->exp_too_high_cnt = 0; |
128 | } else if ((gain + steps) < gain_low && |
129 | exposure > gspca_dev->exposure->minimum) { |
130 | gain = gain_low; |
131 | gspca_dev->exp_too_high_cnt++; |
132 | gspca_dev->exp_too_low_cnt = 0; |
133 | } else { |
134 | gain += steps; |
135 | if (gain > gspca_dev->gain->maximum) |
136 | gain = gspca_dev->gain->maximum; |
137 | else if (gain < gspca_dev->gain->minimum) |
138 | gain = gspca_dev->gain->minimum; |
139 | gspca_dev->exp_too_high_cnt = 0; |
140 | gspca_dev->exp_too_low_cnt = 0; |
141 | } |
142 | |
143 | if (gspca_dev->exp_too_high_cnt > 3) { |
144 | exposure--; |
145 | gspca_dev->exp_too_high_cnt = 0; |
146 | } else if (gspca_dev->exp_too_low_cnt > 3) { |
147 | exposure++; |
148 | gspca_dev->exp_too_low_cnt = 0; |
149 | } |
150 | |
151 | if (gain != orig_gain) { |
152 | v4l2_ctrl_s_ctrl(ctrl: gspca_dev->gain, val: gain); |
153 | retval = 1; |
154 | } |
155 | if (exposure != orig_exposure) { |
156 | v4l2_ctrl_s_ctrl(ctrl: gspca_dev->exposure, val: exposure); |
157 | retval = 1; |
158 | } |
159 | |
160 | if (retval) |
161 | gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n" , |
162 | gain, exposure); |
163 | return retval; |
164 | } |
165 | EXPORT_SYMBOL(gspca_coarse_grained_expo_autogain); |
166 | |