1 /*
2 * Copyright (c) 2022, Alliance for Open Media. All rights reserved
3 *
4 * This source code is subject to the terms of the BSD 3-Clause Clear License
5 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
6 * License was not distributed with this source code in the LICENSE file, you
7 * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
8 * Alliance for Open Media Patent License 1.0 was not distributed with this
9 * source code in the PATENTS file, you can obtain it at
10 * www.aomedia.org/license/patent.
11 */
12 #ifndef COMMON_UTILS_NUMERIC_UTILS_H_
13 #define COMMON_UTILS_NUMERIC_UTILS_H_
14
15 #include <algorithm>
16 #include <cmath>
17 #include <cstdint>
18 #include <limits>
19 #include <string>
20 #include <type_traits>
21
22 #include "absl/log/check.h"
23 #include "absl/log/log.h"
24 #include "absl/status/status.h"
25 #include "absl/strings/str_cat.h"
26 #include "absl/strings/string_view.h"
27 #include "absl/types/span.h"
28 #include "iamf/common/utils/validation_utils.h"
29
30 namespace iamf_tools {
31
32 /*!\brief Sums the input values and checks for overflow.
33 *
34 * \param x_1 First summand.
35 * \param x_2 Second summand.
36 * \param result Sum of the inputs on success.
37 * \return `absl::OkStatus()` on success. `absl::InvalidArgumentError()` when
38 * the sum would cause an overflow in a `uint32_t`.
39 */
40 absl::Status AddUint32CheckOverflow(uint32_t x_1, uint32_t x_2,
41 uint32_t& result);
42
43 /*!\brief Converts float input to Q7.8 format.
44 *
45 * \param value Value to convert.
46 * \param result Converted value if successful. The result is floored to the
47 * nearest Q7.8 value.
48 * \return `absl::OkStatus()` if successful. `absl::UnknownError()` if the input
49 * is not valid in Q7.8 format.
50 */
51 absl::Status FloatToQ7_8(float value, int16_t& result);
52
53 /*!\brief Converts Q7.8 input to float output.
54 *
55 * \param value Value to convert.
56 * \return Converted value.
57 */
58 float Q7_8ToFloat(int16_t value);
59
60 // TODO(b/283281856): Consider removing `FloatToQ0_8()` if it is still an unused
61 // function after the encoder supports resampling parameter
62 // blocks.
63 /*!\brief Converts float input to Q0.8 format.
64 *
65 * \param value Value to convert.
66 * \param result Converted value if successful. The result is floored to the
67 * nearest Q0.8 value.
68 * \return `absl::OkStatus()` if successful. `absl::UnknownError()` if the input
69 * is not valid in Q0.8 format.
70 */
71 absl::Status FloatToQ0_8(float value, uint8_t& result);
72
73 /*!\brief Converts Q0.8 input to float output.
74 *
75 * \param value Value to convert.
76 * \return Converted value.
77 */
78 float Q0_8ToFloat(uint8_t value);
79
80 /*!\brief Typecasts the input value and writes to the output argument if valid.
81 *
82 * The custom `field_name` is used to create a more descriptive error message.
83 * This is inserted surrounded by backticks. When this refers to a user facing
84 * field (i.e. related to `UserMetadata` protos) this should refer to the
85 * user-facing field name.
86 *
87 * \param field_name Field name to insert into the error message.
88 * \param input Value to convert.
89 * \param output Converted value if successful.
90 * \return `absl::OkStatus()` if successful. `absl::InvalidArgumentError()` if
91 * the input is outside the expected range.
92 */
93 template <typename T, typename U>
StaticCastIfInRange(absl::string_view field_name,T input,U & output)94 absl::Status StaticCastIfInRange(absl::string_view field_name, T input,
95 U& output) {
96 constexpr U kMinOutput = std::numeric_limits<U>::min();
97 constexpr U kMaxOutput = std::numeric_limits<U>::max();
98 if (input < kMinOutput || kMaxOutput < input) [[unlikely]] {
99 std::string message =
100 absl::StrCat(field_name, " is outside the expected range of ");
101 if constexpr (std::is_same_v<U, char> || std::is_same_v<U, unsigned char>) {
102 absl::StrAppend(&message, "[0, 255]");
103 } else {
104 absl::StrAppend(&message, "[", kMinOutput, ", ", kMaxOutput, "]");
105 }
106 return absl::InvalidArgumentError(message);
107 }
108 output = static_cast<U>(input);
109 return absl::OkStatus();
110 }
111
112 /*!\brief Creates a 32-bit signed integer from the [1, 4] input `bytes`.
113 *
114 * \param bytes Bytes to convert.
115 * \param output Converted value if successful. The result is left-justified;
116 * the upper `bytes.size()` bytes are set based on the input and the
117 * remaining lower bytes are 0.
118 * \return `absl::OkStatus()` if successful. `absl::InvalidArgumentError()` if
119 * the number of bytes is not in the range of [1, 4].
120 */
121 absl::Status LittleEndianBytesToInt32(absl::Span<const uint8_t> bytes,
122 int32_t& output);
123
124 /*!\brief Creates a 32-bit signed integer from the [1, 4] input `bytes`.
125 *
126 * \param bytes Bytes to convert.
127 * \param output Converted value if successful. The result is left-justified;
128 * the upper `bytes.size()` bytes are set based on the input and the
129 * remaining lower bytes are 0.
130 * \return `absl::OkStatus()` if successful. `absl::InvalidArgumentError()` if
131 * the number of bytes is not in the range of [1, 4].
132 */
133 absl::Status BigEndianBytesToInt32(absl::Span<const uint8_t> bytes,
134 int32_t& output);
135
136 /*!\brief Clips and typecasts the input value and writes to the output argument.
137 *
138 * \param input Value to convert.
139 * \param output Converted value if successful.
140 * \return `absl::OkStatus()` if successful. `absl::InvalidArgumentError()` if
141 * the input is NaN.
142 */
143 absl::Status ClipDoubleToInt32(double input, int32_t& output);
144
145 namespace obu_util_internal {
146
147 constexpr double kMaxInt32PlusOneAsDouble =
148 static_cast<double>(std::numeric_limits<int32_t>::max()) + 1.0;
149
150 } // namespace obu_util_internal
151
152 /*!\brief Normalizes the input value to a floating point in the range [-1, +1].
153 *
154 * Normalizes the input from [std::numeric_limits<int32_t>::min(),
155 * std::numeric_limits<int32_t>::max() + 1] to [-1, +1].
156 *
157 * \param value Value to normalize.
158 * \return Normalized value.
159 */
160 template <typename T>
Int32ToNormalizedFloatingPoint(int32_t value)161 constexpr T Int32ToNormalizedFloatingPoint(int32_t value) {
162 using obu_util_internal::kMaxInt32PlusOneAsDouble;
163 static_assert(std::is_floating_point_v<T>);
164
165 // Perform calculations in double. The final cast to the output type, e.g.
166 // `float` could result in loss of precision. Note that casting `int32_t` to
167 // `double` is lossless; every `int32_t` can be exactly represented.
168 return static_cast<T>(static_cast<double>(value) / kMaxInt32PlusOneAsDouble);
169 }
170
171 /*!\brief Converts normalized floating point input to an `int32_t`.
172 *
173 * Transforms the input from the range of [-1, +1] to the range of
174 * [std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max() +
175 * 1].
176 *
177 * Input is clamped to [-1, +1] before processing. Output is clamped to the
178 * full range of an `int32_t`.
179 *
180 * \param value Normalized floating point value to convert.
181 * \param result Converted value if successful.
182 * \return `absl::OkStatus()` if successful. `absl::InvalidArgumentError()` if
183 * the input is any type of NaN or infinity.
184 */
185 template <typename T>
NormalizedFloatingPointToInt32(T value,int32_t & result)186 absl::Status NormalizedFloatingPointToInt32(T value, int32_t& result) {
187 using obu_util_internal::kMaxInt32PlusOneAsDouble;
188 static_assert(std::is_floating_point_v<T>);
189 if (std::isnan(value) || std::isinf(value)) {
190 return absl::InvalidArgumentError("Input is NaN or infinity.");
191 }
192
193 const double clamped_input =
194 std::clamp(static_cast<double>(value), -1.0, 1.0);
195 // Clip the result to be safe. Although only values near
196 // `std::numeric_limits<int32_t>::max() + 1` will be out of range.
197 return ClipDoubleToInt32(clamped_input * kMaxInt32PlusOneAsDouble, result);
198 }
199
200 /*!\brief Gets the native byte order of the runtime system.
201 *
202 * \return `true` if the runtime system natively uses big endian, `false`
203 * otherwise.
204 */
205 bool IsNativeBigEndian();
206
207 /*!\brief Casts and copies the input span to the output span.
208 *
209 * \param field_name Field name of the vector to insert into the error message.
210 * \param vector_size Size of the vector.
211 * \param reported_size Size reported by associated fields (e.g. "*_size" fields
212 * in the OBU).
213 * \return `absl::OkStatus()` if the size arguments are equivalent.
214 * `absl::InvalidArgumentError()` otherwise.
215 */
216 template <typename T, typename U>
StaticCastSpanIfInRange(absl::string_view field_name,absl::Span<const T> input_data,absl::Span<U> output_data)217 absl::Status StaticCastSpanIfInRange(absl::string_view field_name,
218 absl::Span<const T> input_data,
219 absl::Span<U> output_data) {
220 if (const auto status = ValidateContainerSizeEqual(field_name, input_data,
221 output_data.size());
222 !status.ok()) [[unlikely]] {
223 return status;
224 }
225
226 for (int i = 0; i < input_data.size(); ++i) {
227 const auto status =
228 StaticCastIfInRange(field_name, input_data[i], output_data[i]);
229 if (!status.ok()) [[unlikely]] {
230 return status;
231 }
232 }
233 return absl::OkStatus();
234 }
235 } // namespace iamf_tools
236
237 #endif // COMMON_UTILS_NUMERIC_UTILS_H_
238