/* * 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: * * * @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(); } }