1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   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
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258

media / base / audio_sample_types.h [blame]

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_BASE_AUDIO_SAMPLE_TYPES_H_
#define MEDIA_BASE_AUDIO_SAMPLE_TYPES_H_

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>

// To specify different sample formats, we provide a class for each sample
// format that knows certain things about it, such as the C++ data type used
// to store sample values, min and max values, as well as how to convert to
// and from floating point formats. Each class must satisfy a concept we call
// "SampleTypeTraits", which requires that the following publics are provided:
//   * A type |ValueType| specifying the C++ type for storing sample values
//   * A static constant kMinValue which specifies the minimum sample value
//   * A static constant kMaxValue which specifies the maximum sample value
//   * A static constant kZeroPointValue which specifies the sample value
//     representing an amplitude of zero
//   * A static method ConvertFromFloat() that takes a float sample value and
//     converts it to the corresponding ValueType
//   * A static method ConvertFromDouble() that takes a double sample value and
//     converts it to the corresponding ValueType
//   * A static method ConvertToFloat() that takes a ValueType sample value and
//     converts it to the corresponding float value
//   * A static method ConvertToDouble() that takes a ValueType sample value and
//     converts it to the corresponding double value

namespace media {

// For float or double.
// See also the aliases for commonly used types at the bottom of this file.
template <typename SampleType>
class FloatSampleTypeTraits {
  static_assert(std::is_floating_point<SampleType>::value,
                "Template is only valid for float types.");

 public:
  using ValueType = SampleType;

  static constexpr SampleType kMinValue = -1.0f;
  static constexpr SampleType kMaxValue = +1.0f;
  static constexpr SampleType kZeroPointValue = 0.0f;

  static constexpr SampleType FromFloat(float source_value) {
    return From<float>(source_value);
  }
  static constexpr float ToFloat(SampleType source_value) {
    return To<float>(source_value);
  }
  static constexpr SampleType FromDouble(double source_value) {
    return From<double>(source_value);
  }
  static constexpr double ToDouble(SampleType source_value) {
    return To<double>(source_value);
  }

 private:
  template <typename FloatType>
  static constexpr SampleType From(FloatType source_value) {
    // Apply clipping (aka. clamping). These values are frequently sent to OS
    // level drivers that may not properly handle these values.
    // Note: Passing NaN to `std::clamp()` is UB.
    return std::isnan(source_value)
               ? kMinValue
               : std::clamp(static_cast<SampleType>(source_value), kMinValue,
                            kMaxValue);
  }

  template <typename FloatType>
  static constexpr FloatType To(SampleType source_value) {
    return static_cast<FloatType>(source_value);
  }
};

// Similar to above, but does not apply clipping.
template <typename SampleType>
class FloatSampleTypeTraitsNoClip {
  static_assert(std::is_floating_point<SampleType>::value,
                "Template is only valid for float types.");

 public:
  using ValueType = SampleType;

  static constexpr SampleType kMinValue = -1.0f;
  static constexpr SampleType kMaxValue = +1.0f;
  static constexpr SampleType kZeroPointValue = 0.0f;

  static constexpr SampleType FromFloat(float source_value) {
    return From<float>(source_value);
  }
  static constexpr float ToFloat(SampleType source_value) {
    return To<float>(source_value);
  }
  static constexpr SampleType FromDouble(double source_value) {
    return From<double>(source_value);
  }
  static constexpr double ToDouble(SampleType source_value) {
    return To<double>(source_value);
  }

 private:
  template <typename FloatType>
  static constexpr SampleType From(FloatType source_value) {
    return static_cast<SampleType>(source_value);
  }

  template <typename FloatType>
  static constexpr FloatType To(SampleType source_value) {
    return static_cast<FloatType>(source_value);
  }
};

// For uint8_t, int16_t, int32_t...
// See also the aliases for commonly used types at the bottom of this file.
template <typename SampleType>
class FixedSampleTypeTraits {
  static_assert(std::numeric_limits<SampleType>::is_integer,
                "Template is only valid for integer types.");

 public:
  using ValueType = SampleType;

  static constexpr SampleType kMinValue =
      std::numeric_limits<SampleType>::min();
  static constexpr SampleType kMaxValue =
      std::numeric_limits<SampleType>::max();
  static constexpr SampleType kZeroPointValue =
      (kMinValue == 0) ? (kMaxValue / 2 + 1) : 0;

  static constexpr SampleType FromFloat(float source_value) {
    return From<float>(source_value);
  }
  static constexpr float ToFloat(SampleType source_value) {
    return To<float>(source_value);
  }
  static constexpr SampleType FromDouble(double source_value) {
    return From<double>(source_value);
  }
  static constexpr double ToDouble(SampleType source_value) {
    return To<double>(source_value);
  }

 private:
  // We pre-compute the scaling factors for conversion at compile-time in order
  // to save computation time during runtime.
  template <typename FloatType>
  struct ScalingFactors {
    // Since zero_point_value() is not the exact center between
    // min_value() and max_value(), we apply a different scaling for positive
    // and negative values.
    // Note that due to the limited precision, the FloatType values may not
    // always be able to represent the max and min values of the integer
    // SampleType exactly. This is a concern when using these scale factors for
    // scaling input sample values for conversion. However, since the min value
    // of SampleType is usually of the form -2^N and the max value is usually of
    // the form (+2^N)-1, and due to the fact that the float types store a
    // significand value plus a binary exponent it just so happens that
    // FloatType can usually represent the min value exactly and its
    // representation of the max value is only off by 1, i.e. it quantizes to
    // (+2^N) instead of (+2^N-1).

    static constexpr FloatType kForPositiveInput =
        static_cast<FloatType>(kMaxValue) -
        static_cast<FloatType>(kZeroPointValue);

    // Note: In the below expression, it is important that we cast kMinValue to
    // FloatType _before_ taking the negative of it. For example, for SampleType
    // int32_t, the expression (- kMinValue) would evaluate to
    // -numeric_limits<int32_t>::min(), which falls outside the numeric
    // range, wraps around, and ends up being the same as
    // +numeric_limits<int32_t>::min().
    static constexpr FloatType kForNegativeInput =
        static_cast<FloatType>(kZeroPointValue) -
        static_cast<FloatType>(kMinValue);

    static constexpr FloatType kInverseForPositiveInput =
        1.0f / kForPositiveInput;

    static constexpr FloatType kInverseForNegativeInput =
        1.0f / kForNegativeInput;
  };

  template <typename FloatType>
  static constexpr SampleType From(FloatType source_value) {
    // Note, that the for the case of |source_value| == 1.0, the imprecision of
    // |kScalingFactorForPositive| can lead to a product that is larger than the
    // maximum possible value of SampleType. To ensure this does not happen, we
    // handle the case of |source_value| == 1.0 as part of the clipping check.
    // For all FloatType values smaller than 1.0, the imprecision of
    // |kScalingFactorForPositive| is small enough to not push the scaled
    // |source_value| outside the numeric range of SampleType.

    // The nested if/else structure appears to compile to a
    // better-performing release binary compared to handling the clipping for
    // both positive and negative values first.
    //
    // Inlining the computation formula for multiplication with the scaling
    // factor and addition of |kZeroPointValue| results in better performance
    // for the int16_t case on Arm when compared to storing the scaling factor
    // in a temporary variable and applying it outside of the if-else block.
    //
    // It is important to have the cast to SampleType take place _after_
    // adding |kZeroPointValue|, because the scaled source value may be negative
    // and SampleType may be an unsigned integer type. The result of casting a
    // negative float to an unsigned integer is undefined.
    if (source_value < 0) {
      // Apply clipping (aka. clamping).
      if (source_value <= FloatSampleTypeTraits<float>::kMinValue) {
        return kMinValue;
      }

      return static_cast<SampleType>(
          (source_value * ScalingFactors<FloatType>::kForNegativeInput) +
          static_cast<FloatType>(kZeroPointValue));
    } else {
      // Apply clipping (aka. clamping).
      // As mentioned above, here we must include the case |source_value| == 1.
      if (source_value >= FloatSampleTypeTraits<float>::kMaxValue) {
        return kMaxValue;
      }
      return static_cast<SampleType>(
          (source_value * ScalingFactors<FloatType>::kForPositiveInput) +
          static_cast<FloatType>(kZeroPointValue));
    }
  }

  template <typename FloatType>
  static constexpr FloatType To(SampleType source_value) {
    FloatType offset_value =
        static_cast<FloatType>(source_value - kZeroPointValue);

    // We multiply with the inverse scaling factor instead of dividing by the
    // scaling factor, because multiplication performs faster than division
    // on many platforms.
    return (offset_value < 0.0f)
               ? (offset_value *
                  ScalingFactors<FloatType>::kInverseForNegativeInput)
               : (offset_value *
                  ScalingFactors<FloatType>::kInverseForPositiveInput);
  }
};

// Aliases for commonly used sample formats.
using Float32SampleTypeTraits = FloatSampleTypeTraits<float>;
using Float32SampleTypeTraitsNoClip = FloatSampleTypeTraitsNoClip<float>;
using Float64SampleTypeTraits = FloatSampleTypeTraits<double>;
using UnsignedInt8SampleTypeTraits = FixedSampleTypeTraits<uint8_t>;
using SignedInt16SampleTypeTraits = FixedSampleTypeTraits<int16_t>;
using SignedInt32SampleTypeTraits = FixedSampleTypeTraits<int32_t>;

}  // namespace media

#endif  // MEDIA_BASE_AUDIO_SAMPLE_TYPES_H_