Home | History | Annotate | Download | only in widgets
      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