1 /* 2 * Copyright (C) 2020 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 #ifndef ANALYZER_BASE_SINE_ANALYZER_H 18 #define ANALYZER_BASE_SINE_ANALYZER_H 19 20 #include <algorithm> 21 #include <cctype> 22 #include <iomanip> 23 #include <iostream> 24 25 #include "InfiniteRecording.h" 26 #include "LatencyAnalyzer.h" 27 28 /** 29 * Output a steady sine wave and analyze the return signal. 30 * 31 * Use a cosine transform to measure the predicted magnitude and relative phase of the 32 * looped back sine wave. Then generate a predicted signal and compare with the actual signal. 33 */ 34 class BaseSineAnalyzer : public LoopbackProcessor { 35 public: 36 BaseSineAnalyzer()37 BaseSineAnalyzer() 38 : LoopbackProcessor() 39 , mInfiniteRecording(64 * 1024) {} 40 isOutputEnabled()41 virtual bool isOutputEnabled() { return true; } 42 setMagnitude(double magnitude)43 void setMagnitude(double magnitude) { 44 mMagnitude = magnitude; 45 mScaledTolerance = mMagnitude * getTolerance(); 46 } 47 48 /** 49 * 50 * @return valid phase or kPhaseInvalid=-999 51 */ getPhaseOffset()52 double getPhaseOffset() { 53 ALOGD("%s(), mPhaseOffset = %f\n", __func__, mPhaseOffset); 54 return mPhaseOffset; 55 } 56 getMagnitude()57 double getMagnitude() const { 58 return mMagnitude; 59 } 60 setNoiseAmplitude(double noiseAmplitude)61 void setNoiseAmplitude(double noiseAmplitude) { 62 mNoiseAmplitude = noiseAmplitude; 63 } 64 getNoiseAmplitude()65 double getNoiseAmplitude() const { 66 return mNoiseAmplitude; 67 } 68 getTolerance()69 double getTolerance() { 70 return mTolerance; 71 } 72 setTolerance(double tolerance)73 void setTolerance(double tolerance) { 74 mTolerance = tolerance; 75 } 76 77 // advance and wrap phase incrementInputPhase()78 void incrementInputPhase() { 79 mInputPhase += mPhaseIncrement; 80 if (mInputPhase > M_PI) { 81 mInputPhase -= (2.0 * M_PI); 82 } 83 } 84 85 // advance and wrap phase incrementOutputPhase()86 void incrementOutputPhase() { 87 mOutputPhase += mPhaseIncrement; 88 if (mOutputPhase > M_PI) { 89 mOutputPhase -= (2.0 * M_PI); 90 } 91 } 92 93 94 /** 95 * @param frameData upon return, contains the reference sine wave 96 * @param channelCount 97 */ processOutputFrame(float * frameData,int channelCount)98 result_code processOutputFrame(float *frameData, int channelCount) override { 99 float output = 0.0f; 100 // Output sine wave so we can measure it. 101 if (isOutputEnabled()) { 102 float sinOut = sinf(mOutputPhase); 103 incrementOutputPhase(); 104 output = (sinOut * mOutputAmplitude) 105 + (mWhiteNoise.nextRandomDouble() * getNoiseAmplitude()); 106 // ALOGD("sin(%f) = %f, %f\n", mOutputPhase, sinOut, kPhaseIncrement); 107 } 108 for (int i = 0; i < channelCount; i++) { 109 frameData[i] = (i == getOutputChannel()) ? output : 0.0f; 110 } 111 return RESULT_OK; 112 } 113 114 /** 115 * Calculate the magnitude of the component of the input signal 116 * that matches the analysis frequency. 117 * Also calculate the phase that we can use to create a 118 * signal that matches that component. 119 * The phase will be between -PI and +PI. 120 */ 121 double calculateMagnitudePhase(double *phasePtr = nullptr) { 122 if (mFramesAccumulated == 0) { 123 return 0.0; 124 } 125 double sinMean = mSinAccumulator / mFramesAccumulated; 126 double cosMean = mCosAccumulator / mFramesAccumulated; 127 double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean)); 128 if (phasePtr != nullptr) { 129 double phase; 130 if (magnitude < kMinValidMagnitude) { 131 phase = kPhaseInvalid; 132 ALOGD("%s() mag very low! sinMean = %7.5f, cosMean = %7.5f", 133 __func__, sinMean, cosMean); 134 } else { 135 phase = atan2(cosMean, sinMean); 136 if (phase == 0.0) { 137 ALOGD("%s() phase zero! sinMean = %7.5f, cosMean = %7.5f", 138 __func__, sinMean, cosMean); 139 } 140 } 141 *phasePtr = phase; 142 } 143 return magnitude; 144 } 145 146 /** 147 * Perform sin/cos analysis on each sample. 148 * Measure magnitude and phase on every period. 149 * Updates mPhaseOffset 150 * @param sample 151 * @param referencePhase 152 * @return true if magnitude and phase updated 153 */ transformSample(float sample)154 bool transformSample(float sample) { 155 // Compare incoming signal with the reference input sine wave. 156 mSinAccumulator += static_cast<double>(sample) * sinf(mInputPhase); 157 mCosAccumulator += static_cast<double>(sample) * cosf(mInputPhase); 158 incrementInputPhase(); 159 160 mFramesAccumulated++; 161 // Must be a multiple of the period or the calculation will not be accurate. 162 if (mFramesAccumulated == mSinePeriod) { 163 const double coefficient = 0.1; 164 double magnitude = calculateMagnitudePhase(&mPhaseOffset); 165 166 ALOGD("%s(), phaseOffset = %f\n", __func__, mPhaseOffset); 167 if (mPhaseOffset != kPhaseInvalid) { 168 // One pole averaging filter. 169 setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient)); 170 } 171 resetAccumulator(); 172 return true; 173 } else { 174 return false; 175 } 176 } 177 178 // reset the sine wave detector resetAccumulator()179 virtual void resetAccumulator() { 180 mFramesAccumulated = 0; 181 mSinAccumulator = 0.0; 182 mCosAccumulator = 0.0; 183 } 184 reset()185 void reset() override { 186 LoopbackProcessor::reset(); 187 resetAccumulator(); 188 mMagnitude = 0.0; 189 } 190 prepareToTest()191 void prepareToTest() override { 192 LoopbackProcessor::prepareToTest(); 193 mSinePeriod = getSampleRate() / kTargetGlitchFrequency; 194 mInputPhase = 0.0f; 195 mOutputPhase = 0.0f; 196 mInverseSinePeriod = 1.0 / mSinePeriod; 197 mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod; 198 } 199 200 protected: 201 // Try to get a prime period so the waveform plot changes every time. 202 static constexpr int32_t kTargetGlitchFrequency = 48000 / 113; 203 204 int32_t mSinePeriod = 1; // this will be set before use 205 double mInverseSinePeriod = 1.0; 206 double mPhaseIncrement = 0.0; 207 // Use two sine wave phases, input and output. 208 // This is because the number of input and output samples may differ 209 // in a callback and the output frame count may advance ahead of the input, or visa versa. 210 double mInputPhase = 0.0; 211 double mOutputPhase = 0.0; 212 double mOutputAmplitude = 0.75; 213 // This is the phase offset between the mInputPhase sine wave and the recorded 214 // signal at the tuned frequency. 215 // If this jumps around then we are probably just hearing noise. 216 // Noise can cause the magnitude to be high but mPhaseOffset will be pretty random. 217 // If we are tracking a sine wave then mPhaseOffset should be consistent. 218 double mPhaseOffset = 0.0; 219 // kPhaseInvalid indicates that the phase measurement cannot be used. 220 // We were seeing times when a magnitude of zero was causing atan2(s,c) to 221 // return a phase of zero, which looked valid to Java. This is a way of passing 222 // an error code back to Java as a single value to avoid race conditions. 223 static constexpr double kPhaseInvalid = -999.0; 224 double mMagnitude = 0.0; 225 static constexpr double kMinValidMagnitude = 2.0 / (1 << 16); 226 int32_t mFramesAccumulated = 0; 227 double mSinAccumulator = 0.0; 228 double mCosAccumulator = 0.0; 229 double mScaledTolerance = 0.0; 230 231 InfiniteRecording<float> mInfiniteRecording; 232 233 private: 234 float mTolerance = 0.10; // scaled from 0.0 to 1.0 235 236 float mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC. 237 PseudoRandom mWhiteNoise; 238 }; 239 240 #endif //ANALYZER_BASE_SINE_ANALYZER_H 241