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 mTextColor = Color.CYAN; 35 36 private float mDisplayFontSize = 32f; 37 38 private short[] mPCM16Buffer; 39 private float[] mPCMFloatBuffer; 40 41 private int mNumChannels = 2; 42 private int mNumFrames = 0; 43 44 private boolean mDisplayBufferSize = true; 45 private boolean mDisplayMaxMagnitudes = false; 46 private boolean mDisplayPersistentMaxMagnitude = false; 47 private float mPersistentMaxMagnitude; 48 49 private float[] mPointsBuffer; 50 51 // Horrible kludge 52 private static int mCachedWidth = 0; 53 WaveScopeView(Context context, AttributeSet attrs)54 public WaveScopeView(Context context, AttributeSet attrs) { 55 super(context, attrs); 56 } 57 58 @Override setBackgroundColor(int color)59 public void setBackgroundColor(int color) { mBackgroundColor = color; } 60 setTraceColor(int color)61 public void setTraceColor(int color) { mTraceColor = color; } 62 getDisplayBufferSize()63 public boolean getDisplayBufferSize() { 64 return mDisplayBufferSize; 65 } 66 setDisplayBufferSize(boolean display)67 public void setDisplayBufferSize(boolean display) { 68 mDisplayBufferSize = display; 69 } 70 setDisplayMaxMagnitudes(boolean display)71 public void setDisplayMaxMagnitudes(boolean display) { 72 mDisplayMaxMagnitudes = display; 73 } 74 setDisplayPersistentMaxMagnitude(boolean display)75 public void setDisplayPersistentMaxMagnitude(boolean display) { 76 mDisplayPersistentMaxMagnitude = display; 77 } 78 79 /** 80 * Clears persistent max magnitude so a new value can be calculated. 81 */ resetPersistentMaxMagnitude()82 public void resetPersistentMaxMagnitude() { 83 mPersistentMaxMagnitude = 0.0f; 84 } 85 setPCM16Buff(short[] smpl16Buff, int numChans, int numFrames)86 public void setPCM16Buff(short[] smpl16Buff, int numChans, int numFrames) { 87 mPCM16Buffer = smpl16Buff; 88 mPCMFloatBuffer = null; 89 90 mNumChannels = numChans; 91 mNumFrames = numFrames; 92 93 setupPointBuffer(); 94 95 invalidate(); 96 } 97 setPCMFloatBuff(float[] smplFloatBuff, int numChans, int numFrames)98 public void setPCMFloatBuff(float[] smplFloatBuff, int numChans, int numFrames) { 99 mPCMFloatBuffer = smplFloatBuff; 100 mPCM16Buffer = null; 101 102 mNumChannels = numChans; 103 mNumFrames = numFrames; 104 105 setupPointBuffer(); 106 107 invalidate(); 108 } 109 110 /** 111 * Specifies the number of channels contained in the data buffer to display 112 * @param numChannels 113 */ setNumChannels(int numChannels)114 public void setNumChannels(int numChannels) { 115 mNumChannels = numChannels; 116 setupPointBuffer(); 117 } 118 setupPointBuffer()119 private void setupPointBuffer() { 120 int width = getWidth(); 121 122 // Horrible kludge 123 if (width == 0) { 124 width = mCachedWidth; 125 } else { 126 mCachedWidth = width; 127 } 128 129 // Canvas.drawLines() uses 2 points (float pairs) per line-segment 130 mPointsBuffer = new float[mNumFrames * 4]; 131 132 float xIncr = (float) width / (float) mNumFrames; 133 134 float X = 0; 135 int len = mPointsBuffer.length; 136 for (int pntIndex = 0; pntIndex < len;) { 137 mPointsBuffer[pntIndex] = X; 138 pntIndex += 2; // skip Y 139 140 X += xIncr; 141 142 mPointsBuffer[pntIndex] = X; 143 pntIndex += 2; // skip Y 144 } 145 } 146 147 /** 148 * Draws 1 channel of an interleaved block of SMPL16 samples. 149 * @param cvs The Canvas to draw into. 150 * @param samples The (potentially) multi-channel sample block. 151 * @param numFrames The number of FRAMES in the specified sample block. 152 * @param numChans The number of interleaved channels in the specified sample block. 153 * @param chanIndex The (0-based) index of the channel to draw. 154 * @param zeroY The Y-coordinate of sample value 0 (zero). 155 */ drawChannel16(Canvas cvs, short[] samples, int numFrames, int numChans, int chanIndex, float zeroY)156 private void drawChannel16(Canvas cvs, short[] samples, int numFrames, int numChans, 157 int chanIndex, float zeroY) { 158 float yScale = getHeight() / (float) (Short.MAX_VALUE * 2 * numChans); 159 int pntIndex = 1; // of the first Y coordinate 160 float Y = zeroY; 161 int smplIndex = chanIndex; 162 if (numFrames > mNumFrames) { 163 // This shouldn't happen, but there could be a race condition where a callback 164 // with a larger frame count comes around after changing this view in anticipation 165 // of a smaller count 166 numFrames = mNumFrames; 167 } 168 if (mDisplayMaxMagnitudes) { 169 short maxMagnitude = 0; 170 for (int frame = 0; frame < numFrames; frame++) { 171 mPointsBuffer[pntIndex] = Y; 172 pntIndex += 2; 173 174 short smpl = samples[smplIndex]; 175 if (smpl > maxMagnitude) { 176 maxMagnitude = smpl; 177 } else if (-smpl > maxMagnitude) { 178 maxMagnitude = (short) -smpl; 179 } 180 181 Y = zeroY - (smpl * yScale); 182 183 mPointsBuffer[pntIndex] = Y; 184 pntIndex += 2; 185 186 smplIndex += numChans; 187 } 188 mPaint.setColor(mTextColor); 189 mPaint.setTextSize(mDisplayFontSize); 190 cvs.drawText("" + maxMagnitude, 0, zeroY, mPaint); 191 192 mPaint.setColor(mTraceColor); 193 cvs.drawLines(mPointsBuffer, mPaint); 194 } else { 195 for (int frame = 0; frame < numFrames; frame++) { 196 mPointsBuffer[pntIndex] = Y; 197 pntIndex += 2; 198 199 Y = zeroY - (samples[smplIndex] * yScale); 200 201 mPointsBuffer[pntIndex] = Y; 202 pntIndex += 2; 203 204 smplIndex += numChans; 205 } 206 mPaint.setColor(mTraceColor); 207 cvs.drawLines(mPointsBuffer, mPaint); 208 } 209 } 210 211 /** 212 * Draws 1 channel of an interleaved block of FLOAT samples. 213 * @param cvs The Canvas to draw into. 214 * @param samples The (potentially) multi-channel sample block. 215 * @param numFrames The number of FRAMES in the specified sample block. 216 * @param numChans The number of interleaved channels in the specified sample block. 217 * @param chanIndex The (0-based) index of the channel to draw. 218 * @param zeroY The Y-coordinate of sample value 0 (zero). 219 */ drawChannelFloat(Canvas cvs, float[] samples, int numFrames, int numChans, int chanIndex, float zeroY)220 private void drawChannelFloat(Canvas cvs, float[] samples, int numFrames, int numChans, 221 int chanIndex, float zeroY) { 222 float yScale = getHeight() / (float) (2 * numChans); 223 int pntIndex = 1; // of the first Y coordinate 224 float Y = zeroY; 225 int smplIndex = chanIndex; 226 if (numFrames > mNumFrames) { 227 // This shouldn't happen, but there could be a race condition where a callback 228 // with a larger frame count comes around after changing this view in anticipation 229 // of a smaller count 230 numFrames = mNumFrames; 231 } 232 if (mDisplayMaxMagnitudes) { 233 float maxMagnitude = 0f; 234 for (int frame = 0; frame < numFrames; frame++) { 235 mPointsBuffer[pntIndex] = Y; 236 pntIndex += 2; 237 238 float smpl = samples[smplIndex]; 239 if (smpl > maxMagnitude) { 240 maxMagnitude = smpl; 241 } else if (-smpl > maxMagnitude) { 242 maxMagnitude = -smpl; 243 } 244 245 Y = zeroY - (smpl * yScale); 246 247 mPointsBuffer[pntIndex] = Y; 248 pntIndex += 2; 249 250 smplIndex += numChans; 251 } 252 mPaint.setColor(mTextColor); 253 mPaint.setTextSize(mDisplayFontSize); 254 cvs.drawText("" + maxMagnitude, 0, zeroY, mPaint); 255 256 mPaint.setColor(mTraceColor); 257 cvs.drawLines(mPointsBuffer, mPaint); 258 } else { 259 for (int frame = 0; frame < numFrames; frame++) { 260 mPointsBuffer[pntIndex] = Y; 261 pntIndex += 2; 262 263 Y = zeroY - (samples[smplIndex] * yScale); 264 265 mPointsBuffer[pntIndex] = Y; 266 pntIndex += 2; 267 268 smplIndex += numChans; 269 } 270 mPaint.setColor(mTraceColor); 271 cvs.drawLines(mPointsBuffer, mPaint); 272 } 273 274 if (mDisplayPersistentMaxMagnitude) { 275 smplIndex = chanIndex; 276 for (int frame = 0; frame < numFrames; frame++) { 277 if (samples[smplIndex] > mPersistentMaxMagnitude) { 278 mPersistentMaxMagnitude = samples[smplIndex]; 279 } else if (-samples[smplIndex] > mPersistentMaxMagnitude) { 280 mPersistentMaxMagnitude = -samples[smplIndex]; 281 } 282 283 Y = mDisplayFontSize + (chanIndex * (getHeight() / mNumChannels)); 284 mPaint.setColor(mTextColor); 285 mPaint.setTextSize(mDisplayFontSize); 286 cvs.drawText("" + mPersistentMaxMagnitude, 0, Y, mPaint); 287 } 288 } 289 } 290 291 @Override onDraw(Canvas canvas)292 protected void onDraw(Canvas canvas) { 293 int height = getHeight(); 294 mPaint.setColor(mBackgroundColor); 295 canvas.drawRect(0, 0, getWidth(), height, mPaint); 296 297 if (mDisplayBufferSize) { 298 // Buffer Size 299 mPaint.setColor(mTextColor); 300 mPaint.setTextSize(mDisplayFontSize); 301 canvas.drawText("" + mNumFrames + " frames", 0, height, mPaint); 302 } 303 304 if (mPCM16Buffer != null) { 305 float yOffset = height / (2.0f * mNumChannels); 306 float yDelta = height / (float) mNumChannels; 307 for(int channel = 0; channel < mNumChannels; channel++) { 308 drawChannel16(canvas, mPCM16Buffer, mNumFrames, mNumChannels, channel, yOffset); 309 yOffset += yDelta; 310 } 311 } else if (mPCMFloatBuffer != null) { 312 float yOffset = height / (2.0f * mNumChannels); 313 float yDelta = height / (float) mNumChannels; 314 for(int channel = 0; channel < mNumChannels; channel++) { 315 drawChannelFloat(canvas, mPCMFloatBuffer, mNumFrames, mNumChannels, channel, yOffset); 316 yOffset += yDelta; 317 } 318 } 319 // Log.i("WaveView", "onDraw() - done"); 320 } 321 } 322