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