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