blob: 3ebad00fbaffba4ab41b3d58368e027a4651030a [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 android.media.videoeditor;
import java.io.IOException;
import android.util.Log;
/**
* This class allows to handle an audio track. This audio file is mixed with the
* audio samples of the MediaItems.
* {@hide}
*/
public class AudioTrack {
// Logging
private static final String TAG = "AudioTrack";
// Instance variables
private final String mUniqueId;
private final String mFilename;
private long mStartTimeMs;
private long mTimelineDurationMs;
private int mVolumePercent;
private long mBeginBoundaryTimeMs;
private long mEndBoundaryTimeMs;
private boolean mLoop;
private boolean mMuted;
private final long mDurationMs;
private final int mAudioChannels;
private final int mAudioType;
private final int mAudioBitrate;
private final int mAudioSamplingFrequency;
// Ducking variables
private int mDuckingThreshold;
private int mDuckingLowVolume;
private boolean mIsDuckingEnabled;
// The audio waveform filename
private String mAudioWaveformFilename;
private PlaybackThread mPlaybackThread;
/**
* This listener interface is used by the AudioTrack to emit playback
* progress notifications.
*/
public interface PlaybackProgressListener {
/**
* This method notifies the listener of the current time position while
* playing an audio track
*
* @param audioTrack The audio track
* @param timeMs The current playback position (expressed in milliseconds
* since the beginning of the audio track).
* @param end true if the end of the audio track was reached
*/
public void onProgress(AudioTrack audioTrack, long timeMs, boolean end);
}
/**
* The playback thread
*/
private class PlaybackThread extends Thread {
// Instance variables
private final PlaybackProgressListener mListener;
private final long mFromMs, mToMs;
private boolean mRun;
private final boolean mLoop;
private long mPositionMs;
/**
* Constructor
*
* @param fromMs The time (relative to the beginning of the audio track)
* at which the playback will start
* @param toMs The time (relative to the beginning of the audio track) at
* which the playback will stop. Use -1 to play to the end of
* the audio track
* @param loop true if the playback should be looped once it reaches the
* end
* @param listener The listener which will be notified of the playback
* progress
*/
public PlaybackThread(long fromMs, long toMs, boolean loop,
PlaybackProgressListener listener) {
mPositionMs = mFromMs = fromMs;
if (toMs < 0) {
mToMs = mDurationMs;
} else {
mToMs = toMs;
}
mLoop = loop;
mListener = listener;
mRun = true;
}
/*
* {@inheritDoc}
*/
@Override
public void run() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "===> PlaybackThread.run enter");
}
while (mRun) {
try {
sleep(100);
} catch (InterruptedException ex) {
break;
}
mPositionMs += 100;
if (mPositionMs >= mToMs) {
if (!mLoop) {
if (mListener != null) {
mListener.onProgress(AudioTrack.this, mPositionMs, true);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "PlaybackThread.run playback complete");
}
break;
} else {
// Fire a notification for the end of the clip
if (mListener != null) {
mListener.onProgress(AudioTrack.this, mToMs, false);
}
// Rewind
mPositionMs = mFromMs;
if (mListener != null) {
mListener.onProgress(AudioTrack.this, mPositionMs, false);
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "PlaybackThread.run playback complete");
}
}
} else {
if (mListener != null) {
mListener.onProgress(AudioTrack.this, mPositionMs, false);
}
}
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "===> PlaybackThread.run exit");
}
}
/**
* Stop the playback
*
* @return The stop position
*/
public long stopPlayback() {
mRun = false;
try {
join();
} catch (InterruptedException ex) {
}
return mPositionMs;
}
};
/**
* An object of this type cannot be instantiated by using the default
* constructor
*/
@SuppressWarnings("unused")
private AudioTrack() throws IOException {
this(null, null, null);
}
/**
* Constructor
*
* @param editor The video editor reference
* @param audioTrackId The audio track id
* @param filename The absolute file name
*
* @throws IOException if file is not found
* @throws IllegalArgumentException if file format is not supported or if
* the codec is not supported
*/
public AudioTrack(VideoEditor editor, String audioTrackId, String filename)
throws IOException {
mUniqueId = audioTrackId;
mFilename = filename;
mStartTimeMs = 0;
// TODO: This value represents to the duration of the audio file
mDurationMs = 300000;
// TODO: This value needs to be read from the audio track of the source
// file
mAudioChannels = 2;
mAudioType = MediaProperties.ACODEC_AAC_LC;
mAudioBitrate = 128000;
mAudioSamplingFrequency = 44100;
mTimelineDurationMs = mDurationMs;
mVolumePercent = 100;
// Play the entire audio track
mBeginBoundaryTimeMs = 0;
mEndBoundaryTimeMs = mDurationMs;
// By default loop is disabled
mLoop = false;
// By default the audio track is not muted
mMuted = false;
// Ducking is enabled by default
mDuckingThreshold = 0;
mDuckingLowVolume = 0;
mIsDuckingEnabled = true;
// The audio waveform file is generated later
mAudioWaveformFilename = null;
}
/**
* Constructor
*
* @param editor The video editor reference
* @param audioTrackId The audio track id
* @param filename The audio filename
* @param startTimeMs the start time in milliseconds (relative to the
* timeline)
* @param beginMs start time in the audio track in milliseconds (relative to
* the beginning of the audio track)
* @param endMs end time in the audio track in milliseconds (relative to the
* beginning of the audio track)
* @param loop true to loop the audio track
* @param volume The volume in percentage
* @param muted true if the audio track is muted
* @param audioWaveformFilename The name of the waveform file
*
* @throws IOException if file is not found
*/
AudioTrack(VideoEditor editor, String audioTrackId, String filename, long startTimeMs,
long beginMs, long endMs, boolean loop, int volume, boolean muted,
String audioWaveformFilename) throws IOException {
mUniqueId = audioTrackId;
mFilename = filename;
mStartTimeMs = startTimeMs;
// TODO: This value represents to the duration of the audio file
mDurationMs = 300000;
// TODO: This value needs to be read from the audio track of the source
// file
mAudioChannels = 2;
mAudioType = MediaProperties.ACODEC_AAC_LC;
mAudioBitrate = 128000;
mAudioSamplingFrequency = 44100;
mTimelineDurationMs = endMs - beginMs;
mVolumePercent = volume;
mBeginBoundaryTimeMs = beginMs;
mEndBoundaryTimeMs = endMs;
mLoop = loop;
mMuted = muted;
mAudioWaveformFilename = audioWaveformFilename;
}
/**
* @return The id of the audio track
*/
public String getId() {
return mUniqueId;
}
/**
* Get the filename source for this audio track.
*
* @return The filename as an absolute file name
*/
public String getFilename() {
return mFilename;
}
/**
* @return The number of audio channels in the source of this audio track
*/
public int getAudioChannels() {
return mAudioChannels;
}
/**
* @return The audio codec of the source of this audio track
*/
public int getAudioType() {
return mAudioType;
}
/**
* @return The audio sample frequency of the audio track
*/
public int getAudioSamplingFrequency() {
return mAudioSamplingFrequency;
}
/**
* @return The audio bitrate of the audio track
*/
public int getAudioBitrate() {
return mAudioBitrate;
}
/**
* Set the volume of this audio track as percentage of the volume in the
* original audio source file.
*
* @param volumePercent Percentage of the volume to apply. If it is set to
* 0, then volume becomes mute. It it is set to 100, then volume
* is same as original volume. It it is set to 200, then volume
* is doubled (provided that volume amplification is supported)
*
* @throws UnsupportedOperationException if volume amplification is
* requested and is not supported.
*/
public void setVolume(int volumePercent) {
mVolumePercent = volumePercent;
}
/**
* Get the volume of the audio track as percentage of the volume in the
* original audio source file.
*
* @return The volume in percentage
*/
public int getVolume() {
return mVolumePercent;
}
/**
* @param muted true to mute the audio track
*/
public void setMute(boolean muted) {
mMuted = muted;
}
/**
* @return true if the audio track is muted
*/
public boolean isMuted() {
return mMuted;
}
/**
* Set the start time of this audio track relative to the storyboard
* timeline. Default value is 0.
*
* @param startTimeMs the start time in milliseconds
*/
public void setStartTime(long startTimeMs) {
mStartTimeMs = startTimeMs;
}
/**
* Get the start time of this audio track relative to the storyboard
* timeline.
*
* @return The start time in milliseconds
*/
public long getStartTime() {
return mStartTimeMs;
}
/**
* @return The duration in milliseconds. This value represents the audio
* track duration (not looped)
*/
public long getDuration() {
return mDurationMs;
}
/**
* @return The timeline duration. If looping is enabled this value
* represents the duration of the looped audio track, otherwise it
* is the duration of the audio track (mDurationMs).
*/
public long getTimelineDuration() {
return mTimelineDurationMs;
}
/**
* Sets the start and end marks for trimming an audio track
*
* @param beginMs start time in the audio track in milliseconds (relative to
* the beginning of the audio track)
* @param endMs end time in the audio track in milliseconds (relative to the
* beginning of the audio track)
*/
public void setExtractBoundaries(long beginMs, long endMs) {
if (beginMs > mDurationMs) {
throw new IllegalArgumentException("Invalid start time");
}
if (endMs > mDurationMs) {
throw new IllegalArgumentException("Invalid end time");
}
mBeginBoundaryTimeMs = beginMs;
mEndBoundaryTimeMs = endMs;
if (mLoop) {
// TODO: Compute mDurationMs (from the beginning of the loop until
// the end of all the loops.
mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
} else {
mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
}
}
/**
* @return The boundary begin time
*/
public long getBoundaryBeginTime() {
return mBeginBoundaryTimeMs;
}
/**
* @return The boundary end time
*/
public long getBoundaryEndTime() {
return mEndBoundaryTimeMs;
}
/**
* Enable the loop mode for this audio track. Note that only one of the
* audio tracks in the timeline can have the loop mode enabled. When looping
* is enabled the samples between mBeginBoundaryTimeMs and
* mEndBoundaryTimeMs are looped.
*/
public void enableLoop() {
mLoop = true;
}
/**
* Disable the loop mode
*/
public void disableLoop() {
mLoop = false;
}
/**
* @return true if looping is enabled
*/
public boolean isLooping() {
return mLoop;
}
/**
* Disable the audio duck effect
*/
public void disableDucking() {
mIsDuckingEnabled = false;
}
/**
* TODO DEFINE
*
* @param threshold
* @param lowVolume
* @param volume
*/
public void enableDucking(int threshold, int lowVolume, int volume) {
mDuckingThreshold = threshold;
mDuckingLowVolume = lowVolume;
mIsDuckingEnabled = true;
}
/**
* @return true if ducking is enabled
*/
public boolean isDuckingEnabled() {
return mIsDuckingEnabled;
}
/**
* @return The ducking threshold
*/
public int getDuckingThreshhold() {
return mDuckingThreshold;
}
/**
* @return The ducking low level
*/
public int getDuckingLowVolume() {
return mDuckingLowVolume;
}
/**
* Start the playback of this audio track. This method does not block (does
* not wait for the playback to complete).
*
* @param fromMs The time (relative to the beginning of the audio track) at
* which the playback will start
* @param toMs The time (relative to the beginning of the audio track) at
* which the playback will stop. Use -1 to play to the end of the
* audio track
* @param loop true if the playback should be looped once it reaches the end
* @param listener The listener which will be notified of the playback
* progress
* @throws IllegalArgumentException if fromMs or toMs is beyond the playback
* duration
* @throws IllegalStateException if a playback, preview or an export is
* already in progress
*/
public void startPlayback(long fromMs, long toMs, boolean loop,
PlaybackProgressListener listener) {
if (fromMs >= mDurationMs) {
return;
}
mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, listener);
mPlaybackThread.start();
}
/**
* Stop the audio track playback. This method blocks until the ongoing
* playback is stopped.
*
* @return The accurate current time when stop is effective expressed in
* milliseconds
*/
public long stopPlayback() {
final long stopTimeMs;
if (mPlaybackThread != null) {
stopTimeMs = mPlaybackThread.stopPlayback();
mPlaybackThread = null;
} else {
stopTimeMs = 0;
}
return stopTimeMs;
}
/**
* This API allows to generate a file containing the sample volume levels of
* this audio track object. This function may take significant time and is
* blocking. The filename can be retrieved using getAudioWaveformFilename().
*
* @param listener The progress listener
*
* @throws IOException if the output file cannot be created
* @throws IllegalArgumentException if the audio file does not have a valid
* audio track
*/
public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
throws IOException {
// TODO: Set mAudioWaveformFilename at the end once the extract is
// complete
}
/**
* Get the audio waveform file name if extractAudioWaveform was successful.
* The file format is as following:
* <ul>
* <li>first 4 bytes provide the number of samples for each value, as
* big-endian signed</li>
* <li>4 following bytes is the total number of values in the file, as
* big-endian signed</li>
* <li>then, all values follow as bytes</li>
* </ul>
*
* @return the name of the file, null if the file does not exist
*/
public String getAudioWaveformFilename() {
return mAudioWaveformFilename;
}
/*
* {@inheritDoc}
*/
@Override
public boolean equals(Object object) {
if (!(object instanceof AudioTrack)) {
return false;
}
return mUniqueId.equals(((AudioTrack)object).mUniqueId);
}
/*
* {@inheritDoc}
*/
@Override
public int hashCode() {
return mUniqueId.hashCode();
}
}