blob: 022d1cd809bffd3efd8c48e336eebcace1e3f0be [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.videoeditor.widgets;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.videoeditor.WaveformData;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import com.android.videoeditor.service.MovieAudioTrack;
import com.android.videoeditor.R;
/**
* Audio track view
*/
public class AudioTrackView extends View {
// Instance variables
private final GestureDetector mSimpleGestureDetector;
private final Paint mLinePaint;
private final Paint mLoopPaint;
private final Rect mProgressDestRect;
private final ScrollViewListener mScrollListener;
private double[] mNormalizedGains;
private long mTimelineDurationMs;
private int mProgress;
private ItemSimpleGestureListener mGestureListener;
private WaveformData mWaveformData;
private int mScrollX;
private int mScreenWidth;
/*
* {@inheritDoc}
*/
public AudioTrackView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final Resources resources = getResources();
// Use this Paint for drawing the audio samples
mLinePaint = new Paint();
mLinePaint.setAntiAlias(false);
mLinePaint.setStrokeWidth(1);
mLinePaint.setColor(resources.getColor(R.color.audio_waveform));
// Use this Paint to draw the loop separator
mLoopPaint = new Paint();
mLoopPaint.setAntiAlias(false);
mLoopPaint.setStrokeWidth(1);
mLoopPaint.setColor(resources.getColor(R.color.audio_loop_separator));
// Prepare the bitmap rectangles
final ProgressBar progressBar = ProgressBar.getProgressBar(context);
final int layoutHeight = (int)resources.getDimension(R.dimen.audio_layout_height);
mProgressDestRect = new Rect(getPaddingLeft(),
layoutHeight - progressBar.getHeight() - getPaddingBottom(), 0,
layoutHeight - getPaddingBottom());
// Setup the gesture listener
mSimpleGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
/*
* {@inheritDoc}
*/
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (mGestureListener != null) {
return mGestureListener.onSingleTapConfirmed(AudioTrackView.this, -1,
e);
} else {
return false;
}
}
/*
* {@inheritDoc}
*/
@Override
public void onLongPress (MotionEvent e) {
if (mGestureListener != null) {
mGestureListener.onLongPress(AudioTrackView.this, e);
}
}
});
mScrollListener = new ScrollViewListener() {
@Override
public void onScrollBegin(View view, int scrollX, int scrollY, boolean appScroll) {
}
@Override
public void onScrollProgress(View view, int scrollX, int scrollY, boolean appScroll) {
}
@Override
public void onScrollEnd(View view, int scrollX, int scrollY, boolean appScroll) {
mScrollX = scrollX;
invalidate();
}
};
// Get the screen width
final Display display = ((WindowManager)context.getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay();
final DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
mScreenWidth = metrics.widthPixels;
mProgress = -1;
}
public AudioTrackView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AudioTrackView(Context context) {
this(context, null, 0);
}
@Override
protected void onAttachedToWindow() {
final TimelineHorizontalScrollView scrollView =
(TimelineHorizontalScrollView)((View)((View)getParent()).getParent()).getParent();
mScrollX = scrollView.getScrollX();
scrollView.addScrollListener(mScrollListener);
}
@Override
protected void onDetachedFromWindow() {
final TimelineHorizontalScrollView scrollView =
(TimelineHorizontalScrollView)((View)((View)getParent()).getParent()).getParent();
scrollView.removeScrollListener(mScrollListener);
}
/**
* @param listener The gesture listener
*/
public void setGestureListener(ItemSimpleGestureListener listener) {
mGestureListener = listener;
}
/**
* Set the waveform data
*
* @param waveformData The waveform data
*/
public void setWaveformData(WaveformData waveformData) {
mWaveformData = waveformData;
final int numFrames = mWaveformData.getFramesCount();
final short[] frameGains = mWaveformData.getFrameGains();
final double[] smoothedGains = new double[numFrames];
if (numFrames == 1) {
smoothedGains[0] = frameGains[0];
} else if (numFrames == 2) {
smoothedGains[0] = frameGains[0];
smoothedGains[1] = frameGains[1];
} else if (numFrames > 2) {
smoothedGains[0] = (frameGains[0] / 2.0) + (frameGains[1] / 2.0);
for (int i = 1; i < numFrames - 1; i++) {
smoothedGains[i] =
(frameGains[i - 1] / 3.0) + (frameGains[i] / 3.0) + (frameGains[i + 1] / 3.0);
}
smoothedGains[numFrames - 1] = (frameGains[numFrames - 2] / 2.0) +
(frameGains[numFrames - 1] / 2.0);
}
// Make sure the range is no more than 0 - 255
double maxGain = 1.0;
for (int i = 0; i < numFrames; i++) {
if (smoothedGains[i] > maxGain) {
maxGain = smoothedGains[i];
}
}
double scaleFactor = 1.0;
if (maxGain > 255.0) {
scaleFactor = 255 / maxGain;
}
// Build histogram of 256 bins and figure out the new scaled max
maxGain = 0;
final int gainHist[] = new int[256];
for (int i = 0; i < numFrames; i++) {
int smoothedGain = (int)(smoothedGains[i] * scaleFactor);
if (smoothedGain < 0) {
smoothedGain = 0;
}
if (smoothedGain > 255) {
smoothedGain = 255;
}
if (smoothedGain > maxGain) {
maxGain = smoothedGain;
}
gainHist[smoothedGain]++;
}
// Re-calibrate the minimum to be 5%
double minGain = 0;
int sum = 0;
while (minGain < 255 && sum < numFrames / 20) {
sum += gainHist[(int)minGain];
minGain++;
}
// Re-calibrate the max to be 99%
sum = 0;
while (maxGain > 2 && sum < numFrames / 100) {
sum += gainHist[(int)maxGain];
maxGain--;
}
// Compute the normalized heights
final int halfHeight =
(int)((getResources().getDimension(R.dimen.audio_layout_height) - getPaddingTop() -
getPaddingBottom() - 4) / 2);
final MovieAudioTrack audioTrack = (MovieAudioTrack)getTag();
final int numFramesComp = (int)audioTrack.getDuration() / mWaveformData.getFrameDuration();
mNormalizedGains = new double[Math.max(numFramesComp, numFrames)];
final double range = maxGain - minGain;
for (int i = 0; i < numFrames; i++) {
double value = (smoothedGains[i] * scaleFactor - minGain) / range;
if (value < 0.0) {
value = 0.0;
}
if (value > 1.0) {
value = 1.0;
}
mNormalizedGains[i] = value * value * halfHeight;
}
}
/**
* The project duration has changed
*
* @param timelineDurationMs The new timeline duration
*/
public void updateTimelineDuration(long timelineDurationMs) {
mTimelineDurationMs = timelineDurationMs;
}
/**
* The audio track processing progress
*
* @param progress The progress
*/
public void setProgress(int progress) {
mProgress = progress;
invalidate();
}
/**
* @return The waveform data
*/
public WaveformData getWaveformData() {
return mWaveformData;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mWaveformData == null) {
if (mProgress >= 0) {
ProgressBar.getProgressBar(getContext()).draw(canvas, mProgress,
mProgressDestRect, getPaddingLeft(), getWidth() - getPaddingRight());
}
} else if (mTimelineDurationMs > 0) { // Draw waveform
// Compute the number of frames in the trimmed audio track
final MovieAudioTrack audioTrack = (MovieAudioTrack)getTag();
final int startFrame = (int)(audioTrack.getBoundaryBeginTime() /
mWaveformData.getFrameDuration());
final int numFrames =
(int)(audioTrack.getTimelineDuration() / mWaveformData.getFrameDuration());
final int ctr = getHeight() / 2;
short value;
int index;
final int start = Math.max(mScrollX - mScreenWidth / 2, getPaddingLeft());
final int limit = Math.min(mScrollX + mScreenWidth, getWidth() - getPaddingRight());
if (audioTrack.isAppLooping()) {
// Compute the milliseconds / pixel at the current zoom level
final float framesPerPixel = mTimelineDurationMs /
((float)(mWaveformData.getFrameDuration() *
(((View)getParent()).getWidth() - mScreenWidth)));
for (int i = start; i < limit; i++) {
index = startFrame + (int)(framesPerPixel * i);
index = index % numFrames;
value = (short)mNormalizedGains[index];
canvas.drawLine(i, ctr - value, i, ctr + 1 + value, mLinePaint);
if (index == startFrame) { // Draw the loop delineation
canvas.drawLine(i, getPaddingTop(), i,
getHeight() - getPaddingBottom(), mLinePaint);
}
}
} else {
// Compute the milliseconds / pixel at the current zoom level
final float framesPerPixel = audioTrack.getTimelineDuration() /
((float)(mWaveformData.getFrameDuration() * getWidth()));
for (int i = start; i < limit; i++) {
index = startFrame + (int)(framesPerPixel * i);
value = (short)(mNormalizedGains[index]);
canvas.drawLine(i, ctr - value, i, ctr + 1 + value, mLinePaint);
}
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the gesture detector inspect all events.
mSimpleGestureDetector.onTouchEvent(ev);
super.onTouchEvent(ev);
return true;
}
}