1 /* 2 * Copyright (C) 2021 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.content.res.Resources; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Paint; 25 import android.util.AttributeSet; 26 import android.view.View; 27 28 import com.android.cts.verifier.R; 29 30 /** 31 * Display an audio waveform in a custom View. 32 */ 33 public class WaveformView extends View { 34 private Paint mWavePaint; 35 private int mCurrentWidth; 36 private int mCurrentHeight; 37 private Paint mBackgroundPaint; 38 private float[] mData; 39 private int mSampleCount; 40 private int mSampleOffset; 41 private float mOffsetY; 42 private float mScaleY; 43 private int[] mCursors; 44 private Paint mCursorPaint; 45 WaveformView(Context context, AttributeSet attrs)46 public WaveformView(Context context, AttributeSet attrs) { 47 super(context, attrs); 48 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, 49 R.styleable.WaveformView, 0, 0); 50 init(); 51 } 52 @SuppressWarnings("deprecation") init()53 private void init() { 54 Resources res = getResources(); 55 56 mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 57 mWavePaint.setColor(res.getColor(R.color.waveform_line)); 58 float strokeWidth = res.getDimension(R.dimen.waveform_stroke_width); 59 mWavePaint.setStrokeWidth(strokeWidth); 60 61 mCursorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 62 mCursorPaint.setColor(Color.RED); 63 mCursorPaint.setStrokeWidth(3.0f); 64 65 mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 66 mBackgroundPaint.setColor(res.getColor(R.color.waveform_background)); 67 mBackgroundPaint.setStyle(Paint.Style.FILL); 68 } 69 70 @Override onSizeChanged(int w, int h, int oldw, int oldh)71 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 72 mCurrentWidth = w; 73 mCurrentHeight = h; 74 mOffsetY = 0.5f * h; 75 mScaleY = 0.0f - mOffsetY; 76 } 77 78 @Override onDraw(Canvas canvas)79 protected void onDraw(Canvas canvas) { 80 super.onDraw(canvas); 81 float [] localData = mData; 82 canvas.drawRect(0.0f, 0.0f, mCurrentWidth, 83 mCurrentHeight, mBackgroundPaint); 84 if (localData == null || mSampleCount == 0) { 85 return; 86 } 87 float xScale = ((float) mCurrentWidth) / (mSampleCount - 1); 88 float x0 = 0.0f; 89 if (xScale < 1.0) { 90 // Draw a vertical bar for multiple samples. 91 float ymin = mOffsetY; 92 float ymax = mOffsetY; 93 for (int i = 0; i < mSampleCount; i++) { 94 float x1 = i * xScale; 95 if ((int) x0 != (int) x1) { 96 // draw old data 97 canvas.drawLine(x0, ymin, x0, ymax, mWavePaint); 98 x0 = x1; 99 ymin = mOffsetY; 100 ymax = mOffsetY; 101 } 102 float y1 = (localData[i] * mScaleY) + mOffsetY; 103 ymin = Math.min(ymin, y1); 104 ymax = Math.max(ymax, y1); 105 } 106 } else { 107 // Draw line between samples. 108 float y0 = (localData[0] * mScaleY) + mOffsetY; 109 for (int i = 1; i < mSampleCount; i++) { 110 float x1 = i * xScale; 111 float y1 = (localData[i] * mScaleY) + mOffsetY; 112 canvas.drawLine(x0, y0, x1, y1, mWavePaint); 113 x0 = x1; 114 y0 = y1; 115 } 116 } 117 if (mCursors != null) { 118 for (int i = 0; i < mCursors.length; i++) { 119 float x = mCursors[i] * xScale; 120 canvas.drawLine(x, 0, x, mCurrentHeight, mCursorPaint); 121 } 122 } 123 } 124 125 /** 126 * Copy data into internal buffer then repaint. 127 */ setSampleData(float[] samples)128 public void setSampleData(float[] samples) { 129 setSampleData(samples, 0, samples.length); 130 } 131 132 /** 133 * Sets the sample data to display 134 * @param samples 135 * @param offset 136 * @param count 137 */ setSampleData(float[] samples, int offset, int count)138 public void setSampleData(float[] samples, int offset, int count) { 139 if ((offset + count) > samples.length) { 140 throw new IllegalArgumentException("Exceed array bounds. (" 141 + offset + " + " + count + ") > " + samples.length); 142 } 143 if (mData == null || count > mData.length) { 144 mData = new float[count]; 145 } 146 System.arraycopy(samples, offset, mData, 0, count); 147 mSampleCount = count; 148 mSampleOffset = offset; 149 } 150 151 /** 152 * Clears the display data 153 */ clearSampleData()154 public void clearSampleData() { 155 mData = null; 156 mSampleCount = 0; 157 mSampleOffset = 0; 158 } 159 160 /** 161 * Copy cursor positions into internal buffer then repaint. 162 */ setCursorData(int[] cursors)163 public void setCursorData(int[] cursors) { 164 if (cursors == null) { 165 mCursors = null; 166 } else { 167 if (mCursors == null || cursors.length != mCursors.length) { 168 mCursors = new int[cursors.length]; 169 } 170 System.arraycopy(cursors, 0, mCursors, 0, mCursors.length); 171 } 172 } 173 } 174