xref: /aosp_15_r20/system/media/audio/include/system/audio_effects/aidl_effects_utils.h (revision b9df5ad1c9ac98a7fefaac271a55f7ae3db05414)
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