1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #pragma once
18 #include <aidl/android/hardware/audio/effect/AcousticEchoCanceler.h>
19 #include <aidl/android/hardware/audio/effect/Capability.h>
20 #include <aidl/android/hardware/audio/effect/DynamicsProcessing.h>
21 #include <aidl/android/hardware/audio/effect/Parameter.h>
22 #include <aidl/android/hardware/audio/effect/Range.h>
23 #include <audio_utils/template_utils.h>
24 #include <system/elementwise_op.h>
25
26 #include <optional>
27 #include <set>
28
29 namespace aidl::android::hardware::audio::effect {
30
31 /**
32 * The first AIDL version that introduced the IEffect::reopen method.
33 */
34 static constexpr int32_t kReopenSupportedVersion = 2;
35
36 /**
37 * The first AIDL version that introduced the android.hardware.audio.effect.State.DRAINING state.
38 */
39 static constexpr int32_t kDrainSupportedVersion = 3;
40 /**
41 * The first AIDL version that support effect destroy at any state.
42 */
43 static constexpr int32_t kDestroyAnyStateSupportedVersion = 3;
44
45 /**
46 * EventFlag to indicate that the client has written data to the FMQ, align with
47 * EffectHalAidl.
48 *
49 * This flag is deprecated start from HAL AIDL version 2 and should not be used.
50 * Bit 0x01 and 0x02 were used by FMQ internally (FMQ_NOT_FULL and
51 * FMQ_NOT_EMPTY), using these event flag bits will cause conflict and may
52 * result in a waiter not able to receive wake correctly.
53 */
54 static constexpr uint32_t kEventFlagNotEmpty = 0x1;
55 /**
56 * EventFlag for the effect instance to indicate that the data FMQ needs to be updated.
57 * TODO: b/277900230, Define in future AIDL version.
58 */
59 static constexpr uint32_t kEventFlagDataMqUpdate = 0x1 << 10;
60 /**
61 * EventFlag to indicate that the data FMQ is not Empty after a write.
62 * TODO: b/277900230, Define in future AIDL version.
63 */
64 static constexpr uint32_t kEventFlagDataMqNotEmpty = 0x1 << 11;
65
66 /**
67 * Check the target Parameter with $Parameter$Range definition in Capability.
68 * This method go through the elements in the ranges to find a matching tag for the target
69 * parameter, and check if the target parameter is inside range use the default AIDL union
70 * comparator.
71 *
72 * Absence of a corresponding range is an indication that there are no limits set on the parameter
73 * so this method return true.
74 */
75 template <typename T, typename R>
inRange(const T & target,const R & ranges)76 static inline bool inRange(const T& target, const R& ranges) {
77 for (const auto& r : ranges) {
78 if (target.getTag() == r.min.getTag() &&
79 target.getTag() == r.max.getTag() &&
80 (target < r.min || target > r.max)) {
81 return false;
82 }
83 }
84 return true;
85 }
86
87 template <typename Range::Tag rangeTag, typename T>
inRange(const T & target,const Capability & cap)88 static inline bool inRange(const T& target, const Capability& cap) {
89 if (cap.range.getTag() == rangeTag) {
90 const auto& ranges = cap.range.template get<rangeTag>();
91 return inRange(target, ranges);
92 }
93 return true;
94 }
95
96 /**
97 * Return the range pair (as defined in aidl::android::hardware::audio::effect::Range) of a
98 * parameter.
99 */
100 template <typename Range::Tag RangeTag, typename R, typename T>
getRange(const Capability & cap,T tag)101 static inline std::optional<R> getRange(const Capability& cap, T tag) {
102 if (cap.range.getTag() != RangeTag) {
103 return std::nullopt;
104 }
105
106 const auto& ranges = cap.range.template get<RangeTag>();
107 for (const auto& r : ranges) {
108 if (r.min.getTag() == tag && r.max.getTag() == tag) {
109 return r;
110 }
111 }
112
113 return std::nullopt;
114 }
115
116 template <typename T, typename R>
isRangeValid(const T & tag,const R & ranges)117 static inline bool isRangeValid(const T& tag, const R& ranges) {
118 for (const auto& r : ranges) {
119 if (tag == r.min.getTag() && tag == r.max.getTag()) {
120 return r.min <= r.max;
121 }
122 }
123
124 return true;
125 }
126
127 template <typename Range::Tag rangeTag, typename T>
isRangeValid(const T & paramTag,const Capability & cap)128 static inline bool isRangeValid(const T& paramTag, const Capability& cap) {
129 if (cap.range.getTag() == rangeTag) {
130 const auto& ranges = cap.range.template get<rangeTag>();
131 return isRangeValid(paramTag, ranges);
132 }
133 return true;
134 }
135
136 /**
137 * @brief Clamps a parameter to its valid range with `android::audio_utils::elementwise_clamp`.
138 *
139 * @tparam RangeTag, `Range::dynamicsProcessing` for example.
140 * @tparam SpecificTag The effect specific tag in Parameter,
141 * `Parameter::Specific::dynamicsProcessing` for example.
142 * @param param The parameter to clamp, `DynamicsProcessing` for example.
143 * @param cap The effect capability.
144 * @return Return the clamped parameter on success, `std::nullopt` on any failure.
145 */
146 template <Range::Tag RangeTag, Parameter::Specific::Tag SpecificTag>
147 [[nodiscard]]
clampParameter(const Parameter & param,const Capability & cap)148 static inline std::optional<Parameter> clampParameter(const Parameter& param,
149 const Capability& cap) {
150 if constexpr (RangeTag == Range::vendorExtension) return std::nullopt;
151
152 // field tag must matching to continue
153 if (param.getTag() != Parameter::specific) return std::nullopt;
154
155 Parameter::Specific specific = param.template get<Parameter::specific>();
156 auto effect = specific.template get<SpecificTag>();
157 std::optional<decltype(effect)> clamped = std::nullopt;
158
159 const Range& range = cap.range;
160 // no need to clamp if the range capability not defined
161 if (range.getTag() != RangeTag) return param;
162
163 const auto& ranges = range.template get<RangeTag>();
164 for (const auto& r : ranges) {
165 clamped = ::android::audio_utils::elementwise_clamp(effect, r.min, r.max);
166 if (clamped != std::nullopt) {
167 if (effect != clamped.value()) {
168 ALOGI("%s from \"%s\" to \"%s\"", __func__, effect.toString().c_str(),
169 clamped->toString().c_str());
170 }
171 break;
172 }
173 }
174
175 if (clamped == std::nullopt) return std::nullopt;
176 return Parameter::make<Parameter::specific>(
177 Parameter::Specific::make<SpecificTag>(clamped.value()));
178 }
179
180 /**
181 * Customized comparison for AIDL effect Range classes, the comparison is based on the tag value of
182 * the class.
183 * `VendorExtensionRange` is special because the underlying `VendorExtension` is not an AIDL union,
184 * so we compare the value directly.
185 */
186 template <typename T>
187 struct RangeTagLessThan {
operatorRangeTagLessThan188 bool operator()(const T& a, const T& b) const {
189 if constexpr (std::is_same_v<T, Range::VendorExtensionRange>) return a < b;
190 else return a.min.getTag() < b.min.getTag();
191 }
192 };
193
194 /**
195 * @brief Find the shared capability of two capabilities `cap1` and `cap2`.
196 * A shared range is the intersection part of these two capabilities.
197 *
198 * For example, for below capabilities:
199 * Capability cap1 = {.range = Range::make<Range::volume>({MAKE_RANGE(Volume, levelDb, -4800, 0)})};
200 * Capability cap2 = {.range = Range::make<Range::volume>({MAKE_RANGE(Volume, levelDb, -9600,
201 * -1600)})};
202 * Capability cap3 = {.range = Range::make<Range::volume>({MAKE_RANGE(Volume, levelDb, -800, 0)})};
203 *
204 * The shared capability of cap1 and cap2 is:
205 * Capability{.range = Range::make<Range::volume>({MAKE_RANGE(Volume, levelDb, -4800, -1600)})};
206 * The shared capability of cap1 and cap3 is:
207 * Capability{.range = Range::make<Range::volume>({MAKE_RANGE(Volume, levelDb, -800, 0)})};
208 * The shared capability of cap2 and cap3 is empty so `findSharedCapability` return std::nullopt.
209 *
210 * @param cap1 The first capability
211 * @param cap2 The second capability
212 * @return The shared capability on success, std::nullopt on any failure.
213 */
214 [[nodiscard]]
findSharedCapability(const Capability & cap1,const Capability & cap2)215 static inline std::optional<Capability> findSharedCapability(
216 const Capability& cap1, const Capability& cap2) {
217 if (cap1.range.getTag() != cap2.range.getTag()) return std::nullopt;
218
219 std::optional<Capability> sharedCap = std::nullopt;
220 // RangeTag: tag id of the Effect range, `Range::dynamicsProcessing` for example.
221 // T: type of the effect range, `DynamicsProcessingRange` for example.
222 auto overlapRangeFinder = [&]<Range::Tag RangeTag, typename T>(
223 const std::vector<T>& vec1,
224 const std::vector<T>& vec2) {
225 if constexpr (RangeTag == Range::vendorExtension) {
226 sharedCap = {.range = Range::make<RangeTag>(vec1)};
227 return;
228 }
229
230 if (vec1.empty()) {
231 sharedCap = {.range = Range::make<RangeTag>(vec2)};
232 return;
233 }
234 if (vec2.empty()) {
235 sharedCap = {.range = Range::make<RangeTag>(vec1)};
236 return;
237 }
238
239 std::vector<T> sharedVec;
240 std::set<T, RangeTagLessThan<T>> set2{vec2.begin(), vec2.end()};
241 std::for_each(vec1.begin(), vec1.end(), [&](const auto& v1) {
242 const auto& v2 = set2.find(v1);
243 if (v2 != set2.end()) {
244 auto min = ::android::audio_utils::elementwise_max(v1.min, v2->min);
245 auto max = ::android::audio_utils::elementwise_min(v1.max, v2->max);
246 // only add range to vector when at least min or max is valid
247 if (min != std::nullopt || max != std::nullopt) {
248 using ElementType = decltype(v1.min);
249 sharedVec.emplace_back(T{.min = min.value_or(ElementType{}),
250 .max = max.value_or(ElementType{})});
251 }
252 }
253 });
254 if (!sharedVec.empty()) sharedCap = {.range = Range::make<RangeTag>(sharedVec)};
255 };
256
257 // find the underlying value in these two ranges, and call `overlapRangeFinder` lambda
258 ::android::audio_utils::aidl_union_op(overlapRangeFinder, cap1.range, cap2.range);
259 return sharedCap;
260 }
261
262 } // namespace aidl::android::hardware::audio::effect