1 /* 2 * Copyright (C) 2017 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 package com.android.cts.verifier.audio.audiolib; 18 19 import android.content.Context; 20 import android.graphics.Canvas; 21 import android.graphics.Color; 22 import android.graphics.Paint; 23 import android.util.AttributeSet; 24 import android.view.View; 25 26 public class WaveScopeView extends View { 27 @SuppressWarnings("unused") 28 private static final String TAG = "WaveScopeView"; 29 30 private final Paint mPaint = new Paint(); 31 32 private int mBackgroundColor = Color.WHITE; 33 private int mTraceColor = Color.BLACK; 34 private int mLimitsColor = Color.YELLOW; 35 private int mZeroColor = Color.RED; 36 private int mTextColor = Color.CYAN; 37 38 private float mDisplayFontSize = 32f; 39 40 private float[] mPCMFloatBuffer; 41 42 private int mNumChannels = 2; 43 private int mNumFrames = 0; 44 45 private boolean mDisplayBufferSize = true; 46 private boolean mDisplayMaxMagnitudes = false; 47 private boolean mDisplayPersistentMaxMagnitude = false; 48 private float mPersistentMaxMagnitude; 49 private boolean mDisplayLimits = false; 50 private boolean mDisplayZero = false; 51 private float mVerticalInset = 10.0f; 52 53 private float[] mPointsBuffer; 54 55 // Horrible kludge 56 private static int mCachedWidth = 0; 57 WaveScopeView(Context context, AttributeSet attrs)58 public WaveScopeView(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 } 61 62 @Override setBackgroundColor(int color)63 public void setBackgroundColor(int color) { mBackgroundColor = color; } 64 setTraceColor(int color)65 public void setTraceColor(int color) { 66 mTraceColor = color; 67 } 68 setLimitsColor(int color)69 public void setLimitsColor(int color) { 70 mLimitsColor = color; 71 } 72 setZeroColor(int color)73 public void setZeroColor(int color) { 74 mZeroColor = color; 75 } 76 getDisplayBufferSize()77 public boolean getDisplayBufferSize() { 78 return mDisplayBufferSize; 79 } 80 setDisplayBufferSize(boolean display)81 public void setDisplayBufferSize(boolean display) { 82 mDisplayBufferSize = display; 83 } 84 setDisplayMaxMagnitudes(boolean display)85 public void setDisplayMaxMagnitudes(boolean display) { 86 mDisplayMaxMagnitudes = display; 87 } 88 setDisplayPersistentMaxMagnitude(boolean display)89 public void setDisplayPersistentMaxMagnitude(boolean display) { 90 mDisplayPersistentMaxMagnitude = display; 91 } 92 setDisplayLimits(boolean display)93 public void setDisplayLimits(boolean display) { 94 mDisplayLimits = display; 95 } 96 setDisplayZero(boolean display)97 public void setDisplayZero(boolean display) { 98 mDisplayZero = display; 99 } 100 101 /** 102 * Clears persistent max magnitude so a new value can be calculated. 103 */ resetPersistentMaxMagnitude()104 public void resetPersistentMaxMagnitude() { 105 mPersistentMaxMagnitude = 0.0f; 106 } 107 setPCMFloatBuff(float[] smplFloatBuff, int numChans, int numFrames)108 public void setPCMFloatBuff(float[] smplFloatBuff, int numChans, int numFrames) { 109 mPCMFloatBuffer = smplFloatBuff; 110 111 mNumChannels = numChans; 112 mNumFrames = numFrames; 113 114 setupPointBuffer(); 115 116 invalidate(); 117 } 118 119 /** 120 * Specifies the number of channels contained in the data buffer to display 121 * @param numChannels 122 */ setNumChannels(int numChannels)123 public void setNumChannels(int numChannels) { 124 mNumChannels = numChannels; 125 setupPointBuffer(); 126 } 127 setupPointBuffer()128 private void setupPointBuffer() { 129 int width = getWidth(); 130 131 // Horrible kludge 132 if (width == 0) { 133 width = mCachedWidth; 134 } else { 135 mCachedWidth = width; 136 } 137 138 // Canvas.drawLines() uses 2 points (float pairs) per line-segment 139 // Only reallocate if we need more space. 140 if (mPointsBuffer == null || (mNumFrames * 4) > mPointsBuffer.length) { 141 mPointsBuffer = new float[mNumFrames * 4]; 142 } 143 float xIncr = (float) width / (float) mNumFrames; 144 145 float X = 0; 146 int len = mPointsBuffer.length; 147 for (int pntIndex = 0; pntIndex < len;) { 148 mPointsBuffer[pntIndex] = X; 149 pntIndex += 2; // skip Y 150 151 X += xIncr; 152 153 mPointsBuffer[pntIndex] = X; 154 pntIndex += 2; // skip Y 155 } 156 } 157 158 /** 159 * Draws 1 channel of an interleaved block of FLOAT samples. 160 * @param cvs The Canvas to draw into. 161 * @param samples The (potentially) multi-channel sample block. 162 * @param numFrames The number of FRAMES in the specified sample block. 163 * @param numChans The number of interleaved channels in the specified sample block. 164 * @param chanIndex The (0-based) index of the channel to draw. 165 * @param zeroY The Y-coordinate of sample value 0 (zero). 166 */ 167 private static final int FLOATS_PER_POINT = 2; drawChannelFloat(Canvas cvs, float[] samples, int numFrames, int numChans, int chanIndex, float zeroY)168 private void drawChannelFloat(Canvas cvs, float[] samples, int numFrames, int numChans, 169 int chanIndex, float zeroY) { 170 171 float height = getHeight() - mVerticalInset * 2.0f * (float) numChans; 172 zeroY += mVerticalInset; 173 174 if (mDisplayZero) { 175 mPaint.setColor(mZeroColor); 176 float endX = mPointsBuffer[mPointsBuffer.length - FLOATS_PER_POINT]; 177 cvs.drawLine(0, zeroY, endX, zeroY, mPaint); 178 } 179 180 // 2 here is the range between MIN_FLOAT_SAMPLE (-1.0) and MAX_FLOAT_SAMPLE (1.0) 181 float yScale = height / (float) (2 * numChans); 182 int pntIndex = 1; // of the first Y coordinate 183 int smplIndex = chanIndex; 184 float yCoord = zeroY - (samples[smplIndex] * yScale); 185 // use a local reference to the points in case a realloc rolls around. 186 float[] localPointsBuffer = mPointsBuffer; 187 if (mDisplayMaxMagnitudes) { 188 float maxMagnitude = 0f; 189 // ensure we don't step past the end of the points buffer 190 for (int frame = 0; frame < numFrames; frame++) { 191 localPointsBuffer[pntIndex] = yCoord; 192 pntIndex += FLOATS_PER_POINT; 193 194 float smpl = samples[smplIndex]; 195 if (smpl > maxMagnitude) { 196 maxMagnitude = smpl; 197 } else if (-smpl > maxMagnitude) { 198 maxMagnitude = -smpl; 199 } 200 201 yCoord = zeroY - (smpl * yScale); 202 203 localPointsBuffer[pntIndex] = yCoord; 204 pntIndex += FLOATS_PER_POINT; 205 206 smplIndex += numChans; 207 } 208 mPaint.setColor(mTextColor); 209 mPaint.setTextSize(mDisplayFontSize); 210 cvs.drawText(String.format("%.2f", maxMagnitude), 0, zeroY, mPaint); 211 212 mPaint.setColor(mTraceColor); 213 cvs.drawLines(localPointsBuffer, mPaint); 214 } else { 215 for (int frame = 0; frame < numFrames; frame++) { 216 localPointsBuffer[pntIndex] = yCoord; 217 pntIndex += FLOATS_PER_POINT; 218 219 yCoord = zeroY - (samples[smplIndex] * yScale); 220 221 localPointsBuffer[pntIndex] = yCoord; 222 pntIndex += FLOATS_PER_POINT; 223 224 smplIndex += numChans; 225 } 226 mPaint.setColor(mTraceColor); 227 cvs.drawLines(localPointsBuffer, mPaint); 228 } 229 230 if (mDisplayPersistentMaxMagnitude) { 231 smplIndex = chanIndex; 232 for (int frame = 0; frame < numFrames; frame++) { 233 if (samples[smplIndex] > mPersistentMaxMagnitude) { 234 mPersistentMaxMagnitude = samples[smplIndex]; 235 } else if (-samples[smplIndex] > mPersistentMaxMagnitude) { 236 mPersistentMaxMagnitude = -samples[smplIndex]; 237 } 238 239 yCoord = mDisplayFontSize + (chanIndex * (getHeight() / mNumChannels)); 240 mPaint.setColor(mTextColor); 241 mPaint.setTextSize(mDisplayFontSize); 242 cvs.drawText(String.format("%.2f", mPersistentMaxMagnitude), 0, yCoord, mPaint); 243 } 244 } 245 246 if (mDisplayLimits) { 247 mPaint.setColor(mLimitsColor); 248 float endX = mPointsBuffer[mPointsBuffer.length - FLOATS_PER_POINT]; 249 yCoord = zeroY + yScale; 250 cvs.drawLine(0, yCoord, endX, yCoord, mPaint); 251 yCoord = zeroY + -yScale; 252 cvs.drawLine(0, yCoord, endX, yCoord, mPaint); 253 } 254 } 255 256 @Override onDraw(Canvas canvas)257 protected void onDraw(Canvas canvas) { 258 int height = getHeight(); 259 mPaint.setColor(mBackgroundColor); 260 canvas.drawRect(0, 0, getWidth(), height, mPaint); 261 262 if (mDisplayBufferSize) { 263 // Buffer Size 264 mPaint.setColor(mTextColor); 265 mPaint.setTextSize(mDisplayFontSize); 266 canvas.drawText("" + mNumFrames + " frames", 0, height, mPaint); 267 } 268 269 if (mPCMFloatBuffer != null) { 270 float yOffset = height / (2.0f * mNumChannels); 271 float yDelta = height / (float) mNumChannels; 272 for(int channel = 0; channel < mNumChannels; channel++) { 273 drawChannelFloat(canvas, mPCMFloatBuffer, mNumFrames, mNumChannels, channel, yOffset); 274 yOffset += yDelta; 275 } 276 } 277 } 278 } 279