1 /* 2 * Copyright (C) 2010 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.videoeditor.widgets; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.Canvas; 22 import android.graphics.Paint; 23 import android.graphics.Rect; 24 import android.media.videoeditor.WaveformData; 25 import android.util.AttributeSet; 26 import android.util.DisplayMetrics; 27 import android.view.Display; 28 import android.view.GestureDetector; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.WindowManager; 32 33 import com.android.videoeditor.service.MovieAudioTrack; 34 import com.android.videoeditor.R; 35 36 /** 37 * Audio track view 38 */ 39 public class AudioTrackView extends View { 40 // Instance variables 41 private final GestureDetector mSimpleGestureDetector; 42 private final Paint mLinePaint; 43 private final Paint mLoopPaint; 44 private final Rect mProgressDestRect; 45 private final ScrollViewListener mScrollListener; 46 47 private double[] mNormalizedGains; 48 private long mTimelineDurationMs; 49 private int mProgress; 50 private ItemSimpleGestureListener mGestureListener; 51 private WaveformData mWaveformData; 52 private int mScrollX; 53 private int mScreenWidth; 54 55 /* 56 * {@inheritDoc} 57 */ 58 public AudioTrackView(Context context, AttributeSet attrs, int defStyle) { 59 super(context, attrs, defStyle); 60 61 final Resources resources = getResources(); 62 63 // Use this Paint for drawing the audio samples 64 mLinePaint = new Paint(); 65 mLinePaint.setAntiAlias(false); 66 mLinePaint.setStrokeWidth(1); 67 mLinePaint.setColor(resources.getColor(R.color.audio_waveform)); 68 69 // Use this Paint to draw the loop separator 70 mLoopPaint = new Paint(); 71 mLoopPaint.setAntiAlias(false); 72 mLoopPaint.setStrokeWidth(1); 73 mLoopPaint.setColor(resources.getColor(R.color.audio_loop_separator)); 74 75 // Prepare the bitmap rectangles 76 final ProgressBar progressBar = ProgressBar.getProgressBar(context); 77 final int layoutHeight = (int)resources.getDimension(R.dimen.audio_layout_height); 78 mProgressDestRect = new Rect(getPaddingLeft(), 79 layoutHeight - progressBar.getHeight() - getPaddingBottom(), 0, 80 layoutHeight - getPaddingBottom()); 81 82 // Setup the gesture listener 83 mSimpleGestureDetector = new GestureDetector(context, 84 new GestureDetector.SimpleOnGestureListener() { 85 /* 86 * {@inheritDoc} 87 */ 88 @Override 89 public boolean onSingleTapConfirmed(MotionEvent e) { 90 if (mGestureListener != null) { 91 return mGestureListener.onSingleTapConfirmed(AudioTrackView.this, -1, 92 e); 93 } else { 94 return false; 95 } 96 } 97 98 /* 99 * {@inheritDoc} 100 */ 101 @Override 102 public void onLongPress (MotionEvent e) { 103 if (mGestureListener != null) { 104 mGestureListener.onLongPress(AudioTrackView.this, e); 105 } 106 } 107 }); 108 109 mScrollListener = new ScrollViewListener() { 110 @Override 111 public void onScrollBegin(View view, int scrollX, int scrollY, boolean appScroll) { 112 } 113 114 @Override 115 public void onScrollProgress(View view, int scrollX, int scrollY, boolean appScroll) { 116 } 117 118 @Override 119 public void onScrollEnd(View view, int scrollX, int scrollY, boolean appScroll) { 120 mScrollX = scrollX; 121 invalidate(); 122 } 123 }; 124 125 // Get the screen width 126 final Display display = ((WindowManager)context.getSystemService( 127 Context.WINDOW_SERVICE)).getDefaultDisplay(); 128 final DisplayMetrics metrics = new DisplayMetrics(); 129 display.getMetrics(metrics); 130 mScreenWidth = metrics.widthPixels; 131 132 mProgress = -1; 133 } 134 135 public AudioTrackView(Context context, AttributeSet attrs) { 136 this(context, attrs, 0); 137 } 138 139 public AudioTrackView(Context context) { 140 this(context, null, 0); 141 } 142 143 @Override 144 protected void onAttachedToWindow() { 145 final TimelineHorizontalScrollView scrollView = 146 (TimelineHorizontalScrollView)((View)((View)getParent()).getParent()).getParent(); 147 mScrollX = scrollView.getScrollX(); 148 scrollView.addScrollListener(mScrollListener); 149 } 150 151 @Override 152 protected void onDetachedFromWindow() { 153 final TimelineHorizontalScrollView scrollView = 154 (TimelineHorizontalScrollView)((View)((View)getParent()).getParent()).getParent(); 155 scrollView.removeScrollListener(mScrollListener); 156 } 157 158 /** 159 * @param listener The gesture listener 160 */ 161 public void setGestureListener(ItemSimpleGestureListener listener) { 162 mGestureListener = listener; 163 } 164 165 /** 166 * Set the waveform data 167 * 168 * @param waveformData The waveform data 169 */ 170 public void setWaveformData(WaveformData waveformData) { 171 mWaveformData = waveformData; 172 final int numFrames = mWaveformData.getFramesCount(); 173 final short[] frameGains = mWaveformData.getFrameGains(); 174 final double[] smoothedGains = new double[numFrames]; 175 176 if (numFrames == 1) { 177 smoothedGains[0] = frameGains[0]; 178 } else if (numFrames == 2) { 179 smoothedGains[0] = frameGains[0]; 180 smoothedGains[1] = frameGains[1]; 181 } else if (numFrames > 2) { 182 smoothedGains[0] = (frameGains[0] / 2.0) + (frameGains[1] / 2.0); 183 for (int i = 1; i < numFrames - 1; i++) { 184 smoothedGains[i] = 185 (frameGains[i - 1] / 3.0) + (frameGains[i] / 3.0) + (frameGains[i + 1] / 3.0); 186 } 187 smoothedGains[numFrames - 1] = (frameGains[numFrames - 2] / 2.0) + 188 (frameGains[numFrames - 1] / 2.0); 189 } 190 191 // Make sure the range is no more than 0 - 255 192 double maxGain = 1.0; 193 for (int i = 0; i < numFrames; i++) { 194 if (smoothedGains[i] > maxGain) { 195 maxGain = smoothedGains[i]; 196 } 197 } 198 199 double scaleFactor = 1.0; 200 if (maxGain > 255.0) { 201 scaleFactor = 255 / maxGain; 202 } 203 204 // Build histogram of 256 bins and figure out the new scaled max 205 maxGain = 0; 206 final int gainHist[] = new int[256]; 207 for (int i = 0; i < numFrames; i++) { 208 int smoothedGain = (int)(smoothedGains[i] * scaleFactor); 209 if (smoothedGain < 0) { 210 smoothedGain = 0; 211 } 212 if (smoothedGain > 255) { 213 smoothedGain = 255; 214 } 215 216 if (smoothedGain > maxGain) { 217 maxGain = smoothedGain; 218 } 219 220 gainHist[smoothedGain]++; 221 } 222 223 // Re-calibrate the minimum to be 5% 224 double minGain = 0; 225 int sum = 0; 226 while (minGain < 255 && sum < numFrames / 20) { 227 sum += gainHist[(int)minGain]; 228 minGain++; 229 } 230 231 // Re-calibrate the max to be 99% 232 sum = 0; 233 while (maxGain > 2 && sum < numFrames / 100) { 234 sum += gainHist[(int)maxGain]; 235 maxGain--; 236 } 237 238 // Compute the normalized heights 239 final int halfHeight = 240 (int)((getResources().getDimension(R.dimen.audio_layout_height) - getPaddingTop() - 241 getPaddingBottom() - 4) / 2); 242 final MovieAudioTrack audioTrack = (MovieAudioTrack)getTag(); 243 244 final int numFramesComp = (int)audioTrack.getDuration() / mWaveformData.getFrameDuration(); 245 mNormalizedGains = new double[Math.max(numFramesComp, numFrames)]; 246 final double range = maxGain - minGain; 247 for (int i = 0; i < numFrames; i++) { 248 double value = (smoothedGains[i] * scaleFactor - minGain) / range; 249 if (value < 0.0) { 250 value = 0.0; 251 } 252 253 if (value > 1.0) { 254 value = 1.0; 255 } 256 257 mNormalizedGains[i] = value * value * halfHeight; 258 } 259 } 260 261 /** 262 * The project duration has changed 263 * 264 * @param timelineDurationMs The new timeline duration 265 */ 266 public void updateTimelineDuration(long timelineDurationMs) { 267 mTimelineDurationMs = timelineDurationMs; 268 } 269 270 /** 271 * The audio track processing progress 272 * 273 * @param progress The progress 274 */ 275 public void setProgress(int progress) { 276 mProgress = progress; 277 278 invalidate(); 279 } 280 281 /** 282 * @return The waveform data 283 */ 284 public WaveformData getWaveformData() { 285 return mWaveformData; 286 } 287 288 @Override 289 protected void onDraw(Canvas canvas) { 290 super.onDraw(canvas); 291 292 if (mWaveformData == null) { 293 if (mProgress >= 0) { 294 ProgressBar.getProgressBar(getContext()).draw(canvas, mProgress, 295 mProgressDestRect, getPaddingLeft(), getWidth() - getPaddingRight()); 296 } 297 } else if (mTimelineDurationMs > 0) { // Draw waveform 298 // Compute the number of frames in the trimmed audio track 299 final MovieAudioTrack audioTrack = (MovieAudioTrack)getTag(); 300 final int startFrame = (int)(audioTrack.getBoundaryBeginTime() / 301 mWaveformData.getFrameDuration()); 302 final int numFrames = 303 (int)(audioTrack.getTimelineDuration() / mWaveformData.getFrameDuration()); 304 305 final int ctr = getHeight() / 2; 306 short value; 307 int index; 308 final int start = Math.max(mScrollX - mScreenWidth / 2, getPaddingLeft()); 309 final int limit = Math.min(mScrollX + mScreenWidth, getWidth() - getPaddingRight()); 310 if (audioTrack.isAppLooping()) { 311 // Compute the milliseconds / pixel at the current zoom level 312 final float framesPerPixel = mTimelineDurationMs / 313 ((float)(mWaveformData.getFrameDuration() * 314 (((View)getParent()).getWidth() - mScreenWidth))); 315 316 for (int i = start; i < limit; i++) { 317 index = startFrame + (int)(framesPerPixel * i); 318 index = index % numFrames; 319 value = (short)mNormalizedGains[index]; 320 canvas.drawLine(i, ctr - value, i, ctr + 1 + value, mLinePaint); 321 322 if (index == startFrame) { // Draw the loop delineation 323 canvas.drawLine(i, getPaddingTop(), i, 324 getHeight() - getPaddingBottom(), mLinePaint); 325 } 326 } 327 } else { 328 // Compute the milliseconds / pixel at the current zoom level 329 final float framesPerPixel = audioTrack.getTimelineDuration() / 330 ((float)(mWaveformData.getFrameDuration() * getWidth())); 331 332 for (int i = start; i < limit; i++) { 333 index = startFrame + (int)(framesPerPixel * i); 334 value = (short)(mNormalizedGains[index]); 335 canvas.drawLine(i, ctr - value, i, ctr + 1 + value, mLinePaint); 336 } 337 } 338 } 339 } 340 341 @Override 342 public boolean onTouchEvent(MotionEvent ev) { 343 // Let the gesture detector inspect all events. 344 mSimpleGestureDetector.onTouchEvent(ev); 345 346 super.onTouchEvent(ev); 347 return true; 348 } 349 } 350