AudioTrack.java revision 0ca0a12c6b49032065bf64e9f1cdebf765a0df9d
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
17package android.media.videoeditor;
18
19import java.io.IOException;
20
21import android.util.Log;
22
23/**
24 * This class allows to handle an audio track. This audio file is mixed with the
25 * audio samples of the MediaItems.
26 * {@hide}
27 */
28public class AudioTrack {
29    // Logging
30    private static final String TAG = "AudioTrack";
31
32    // Instance variables
33    private final String mUniqueId;
34    private final String mFilename;
35    private long mStartTimeMs;
36    private long mTimelineDurationMs;
37    private int mVolumePercent;
38    private long mBeginBoundaryTimeMs;
39    private long mEndBoundaryTimeMs;
40    private boolean mLoop;
41    private boolean mMuted;
42
43    private final long mDurationMs;
44    private final int mAudioChannels;
45    private final int mAudioType;
46    private final int mAudioBitrate;
47    private final int mAudioSamplingFrequency;
48
49    // Ducking variables
50    private int mDuckingThreshold;
51    private int mDuckingLowVolume;
52    private boolean mIsDuckingEnabled;
53
54    // The audio waveform filename
55    private String mAudioWaveformFilename;
56    private PlaybackThread mPlaybackThread;
57
58    /**
59     * This listener interface is used by the AudioTrack to emit playback
60     * progress notifications.
61     */
62    public interface PlaybackProgressListener {
63        /**
64         * This method notifies the listener of the current time position while
65         * playing an audio track
66         *
67         * @param audioTrack The audio track
68         * @param timeMs The current playback position (expressed in milliseconds
69         *            since the beginning of the audio track).
70         * @param end true if the end of the audio track was reached
71         */
72        public void onProgress(AudioTrack audioTrack, long timeMs, boolean end);
73    }
74
75    /**
76     * The playback thread
77     */
78    private class PlaybackThread extends Thread {
79        // Instance variables
80        private final PlaybackProgressListener mListener;
81        private final long mFromMs, mToMs;
82        private boolean mRun;
83        private final boolean mLoop;
84        private long mPositionMs;
85
86        /**
87         * Constructor
88         *
89         * @param fromMs The time (relative to the beginning of the audio track)
90         *            at which the playback will start
91         * @param toMs The time (relative to the beginning of the audio track) at
92         *            which the playback will stop. Use -1 to play to the end of
93         *            the audio track
94         * @param loop true if the playback should be looped once it reaches the
95         *            end
96         * @param listener The listener which will be notified of the playback
97         *            progress
98         */
99        public PlaybackThread(long fromMs, long toMs, boolean loop,
100                PlaybackProgressListener listener) {
101            mPositionMs = mFromMs = fromMs;
102            if (toMs < 0) {
103                mToMs = mDurationMs;
104            } else {
105                mToMs = toMs;
106            }
107            mLoop = loop;
108            mListener = listener;
109            mRun = true;
110        }
111
112        /*
113         * {@inheritDoc}
114         */
115        @Override
116        public void run() {
117            if (Log.isLoggable(TAG, Log.DEBUG)) {
118                Log.d(TAG, "===> PlaybackThread.run enter");
119            }
120
121            while (mRun) {
122                try {
123                    sleep(100);
124                } catch (InterruptedException ex) {
125                    break;
126                }
127
128                mPositionMs += 100;
129
130                if (mPositionMs >= mToMs) {
131                    if (!mLoop) {
132                        if (mListener != null) {
133                            mListener.onProgress(AudioTrack.this, mPositionMs, true);
134                        }
135                        if (Log.isLoggable(TAG, Log.DEBUG)) {
136                            Log.d(TAG, "PlaybackThread.run playback complete");
137                        }
138                        break;
139                    } else {
140                        // Fire a notification for the end of the clip
141                        if (mListener != null) {
142                            mListener.onProgress(AudioTrack.this, mToMs, false);
143                        }
144
145                        // Rewind
146                        mPositionMs = mFromMs;
147                        if (mListener != null) {
148                            mListener.onProgress(AudioTrack.this, mPositionMs, false);
149                        }
150                        if (Log.isLoggable(TAG, Log.DEBUG)) {
151                            Log.d(TAG, "PlaybackThread.run playback complete");
152                        }
153                    }
154                } else {
155                    if (mListener != null) {
156                        mListener.onProgress(AudioTrack.this, mPositionMs, false);
157                    }
158                }
159            }
160            if (Log.isLoggable(TAG, Log.DEBUG)) {
161                Log.d(TAG, "===> PlaybackThread.run exit");
162            }
163        }
164
165        /**
166         * Stop the playback
167         *
168         * @return The stop position
169         */
170        public long stopPlayback() {
171            mRun = false;
172            try {
173                join();
174            } catch (InterruptedException ex) {
175            }
176            return mPositionMs;
177        }
178    };
179
180    /**
181     * An object of this type cannot be instantiated by using the default
182     * constructor
183     */
184    @SuppressWarnings("unused")
185    private AudioTrack() throws IOException {
186        this(null, null, null);
187    }
188
189    /**
190     * Constructor
191     *
192     * @param editor The video editor reference
193     * @param audioTrackId The audio track id
194     * @param filename The absolute file name
195     *
196     * @throws IOException if file is not found
197     * @throws IllegalArgumentException if file format is not supported or if
198     *             the codec is not supported
199     */
200    public AudioTrack(VideoEditor editor, String audioTrackId, String filename)
201            throws IOException {
202        mUniqueId = audioTrackId;
203        mFilename = filename;
204        mStartTimeMs = 0;
205        // TODO: This value represents to the duration of the audio file
206        mDurationMs = 300000;
207        // TODO: This value needs to be read from the audio track of the source
208        // file
209        mAudioChannels = 2;
210        mAudioType = MediaProperties.ACODEC_AAC_LC;
211        mAudioBitrate = 128000;
212        mAudioSamplingFrequency = 44100;
213
214        mTimelineDurationMs = mDurationMs;
215        mVolumePercent = 100;
216
217        // Play the entire audio track
218        mBeginBoundaryTimeMs = 0;
219        mEndBoundaryTimeMs = mDurationMs;
220
221        // By default loop is disabled
222        mLoop = false;
223
224        // By default the audio track is not muted
225        mMuted = false;
226
227        // Ducking is enabled by default
228        mDuckingThreshold = 0;
229        mDuckingLowVolume = 0;
230        mIsDuckingEnabled = true;
231
232        // The audio waveform file is generated later
233        mAudioWaveformFilename = null;
234    }
235
236    /**
237     * Constructor
238     *
239     * @param editor The video editor reference
240     * @param audioTrackId The audio track id
241     * @param filename The audio filename
242     * @param startTimeMs the start time in milliseconds (relative to the
243     *              timeline)
244     * @param beginMs start time in the audio track in milliseconds (relative to
245     *            the beginning of the audio track)
246     * @param endMs end time in the audio track in milliseconds (relative to the
247     *            beginning of the audio track)
248     * @param loop true to loop the audio track
249     * @param volume The volume in percentage
250     * @param muted true if the audio track is muted
251     * @param audioWaveformFilename The name of the waveform file
252     *
253     * @throws IOException if file is not found
254     */
255    AudioTrack(VideoEditor editor, String audioTrackId, String filename, long startTimeMs,
256            long beginMs, long endMs, boolean loop, int volume, boolean muted,
257            String audioWaveformFilename) throws IOException {
258        mUniqueId = audioTrackId;
259        mFilename = filename;
260        mStartTimeMs = startTimeMs;
261
262        // TODO: This value represents to the duration of the audio file
263        mDurationMs = 300000;
264
265        // TODO: This value needs to be read from the audio track of the source
266        // file
267        mAudioChannels = 2;
268        mAudioType = MediaProperties.ACODEC_AAC_LC;
269        mAudioBitrate = 128000;
270        mAudioSamplingFrequency = 44100;
271
272        mTimelineDurationMs = endMs - beginMs;
273        mVolumePercent = volume;
274
275        mBeginBoundaryTimeMs = beginMs;
276        mEndBoundaryTimeMs = endMs;
277
278        mLoop = loop;
279        mMuted = muted;
280
281        mAudioWaveformFilename = audioWaveformFilename;
282    }
283
284    /**
285     * @return The id of the audio track
286     */
287    public String getId() {
288        return mUniqueId;
289    }
290
291    /**
292     * Get the filename source for this audio track.
293     *
294     * @return The filename as an absolute file name
295     */
296    public String getFilename() {
297        return mFilename;
298    }
299
300    /**
301     * @return The number of audio channels in the source of this audio track
302     */
303    public int getAudioChannels() {
304        return mAudioChannels;
305    }
306
307    /**
308     * @return The audio codec of the source of this audio track
309     */
310    public int getAudioType() {
311        return mAudioType;
312    }
313
314    /**
315     * @return The audio sample frequency of the audio track
316     */
317    public int getAudioSamplingFrequency() {
318        return mAudioSamplingFrequency;
319    }
320
321    /**
322     * @return The audio bitrate of the audio track
323     */
324    public int getAudioBitrate() {
325        return mAudioBitrate;
326    }
327
328    /**
329     * Set the volume of this audio track as percentage of the volume in the
330     * original audio source file.
331     *
332     * @param volumePercent Percentage of the volume to apply. If it is set to
333     *            0, then volume becomes mute. It it is set to 100, then volume
334     *            is same as original volume. It it is set to 200, then volume
335     *            is doubled (provided that volume amplification is supported)
336     *
337     * @throws UnsupportedOperationException if volume amplification is
338     *             requested and is not supported.
339     */
340    public void setVolume(int volumePercent) {
341        mVolumePercent = volumePercent;
342    }
343
344    /**
345     * Get the volume of the audio track as percentage of the volume in the
346     * original audio source file.
347     *
348     * @return The volume in percentage
349     */
350    public int getVolume() {
351        return mVolumePercent;
352    }
353
354    /**
355     * @param muted true to mute the audio track
356     */
357    public void setMute(boolean muted) {
358        mMuted = muted;
359    }
360
361    /**
362     * @return true if the audio track is muted
363     */
364    public boolean isMuted() {
365        return mMuted;
366    }
367
368    /**
369     * Set the start time of this audio track relative to the storyboard
370     * timeline. Default value is 0.
371     *
372     * @param startTimeMs the start time in milliseconds
373     */
374    public void setStartTime(long startTimeMs) {
375        mStartTimeMs = startTimeMs;
376    }
377
378    /**
379     * Get the start time of this audio track relative to the storyboard
380     * timeline.
381     *
382     * @return The start time in milliseconds
383     */
384    public long getStartTime() {
385        return mStartTimeMs;
386    }
387
388    /**
389     * @return The duration in milliseconds. This value represents the audio
390     *         track duration (not looped)
391     */
392    public long getDuration() {
393        return mDurationMs;
394    }
395
396    /**
397     * @return The timeline duration. If looping is enabled this value
398     *         represents the duration of the looped audio track, otherwise it
399     *         is the duration of the audio track (mDurationMs).
400     */
401    public long getTimelineDuration() {
402        return mTimelineDurationMs;
403    }
404
405    /**
406     * Sets the start and end marks for trimming an audio track
407     *
408     * @param beginMs start time in the audio track in milliseconds (relative to
409     *            the beginning of the audio track)
410     * @param endMs end time in the audio track in milliseconds (relative to the
411     *            beginning of the audio track)
412     */
413    public void setExtractBoundaries(long beginMs, long endMs) {
414        if (beginMs > mDurationMs) {
415            throw new IllegalArgumentException("Invalid start time");
416        }
417        if (endMs > mDurationMs) {
418            throw new IllegalArgumentException("Invalid end time");
419        }
420
421        mBeginBoundaryTimeMs = beginMs;
422        mEndBoundaryTimeMs = endMs;
423        if (mLoop) {
424            // TODO: Compute mDurationMs (from the beginning of the loop until
425            // the end of all the loops.
426            mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
427        } else {
428            mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
429        }
430    }
431
432    /**
433     * @return The boundary begin time
434     */
435    public long getBoundaryBeginTime() {
436        return mBeginBoundaryTimeMs;
437    }
438
439    /**
440     * @return The boundary end time
441     */
442    public long getBoundaryEndTime() {
443        return mEndBoundaryTimeMs;
444    }
445
446    /**
447     * Enable the loop mode for this audio track. Note that only one of the
448     * audio tracks in the timeline can have the loop mode enabled. When looping
449     * is enabled the samples between mBeginBoundaryTimeMs and
450     * mEndBoundaryTimeMs are looped.
451     */
452    public void enableLoop() {
453        mLoop = true;
454    }
455
456    /**
457     * Disable the loop mode
458     */
459    public void disableLoop() {
460        mLoop = false;
461    }
462
463    /**
464     * @return true if looping is enabled
465     */
466    public boolean isLooping() {
467        return mLoop;
468    }
469
470    /**
471     * Disable the audio duck effect
472     */
473    public void disableDucking() {
474        mIsDuckingEnabled = false;
475    }
476
477    /**
478     * TODO DEFINE
479     *
480     * @param threshold
481     * @param lowVolume
482     * @param volume
483     */
484    public void enableDucking(int threshold, int lowVolume, int volume) {
485        mDuckingThreshold = threshold;
486        mDuckingLowVolume = lowVolume;
487        mIsDuckingEnabled = true;
488    }
489
490    /**
491     * @return true if ducking is enabled
492     */
493    public boolean isDuckingEnabled() {
494        return mIsDuckingEnabled;
495    }
496
497    /**
498     * @return The ducking threshold
499     */
500    public int getDuckingThreshhold() {
501        return mDuckingThreshold;
502    }
503
504    /**
505     * @return The ducking low level
506     */
507    public int getDuckingLowVolume() {
508        return mDuckingLowVolume;
509    }
510
511    /**
512     * Start the playback of this audio track. This method does not block (does
513     * not wait for the playback to complete).
514     *
515     * @param fromMs The time (relative to the beginning of the audio track) at
516     *            which the playback will start
517     * @param toMs The time (relative to the beginning of the audio track) at
518     *            which the playback will stop. Use -1 to play to the end of the
519     *            audio track
520     * @param loop true if the playback should be looped once it reaches the end
521     * @param listener The listener which will be notified of the playback
522     *            progress
523     * @throws IllegalArgumentException if fromMs or toMs is beyond the playback
524     *             duration
525     * @throws IllegalStateException if a playback, preview or an export is
526     *             already in progress
527     */
528    public void startPlayback(long fromMs, long toMs, boolean loop,
529            PlaybackProgressListener listener) {
530        if (fromMs >= mDurationMs) {
531            return;
532        }
533        mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, listener);
534        mPlaybackThread.start();
535    }
536
537    /**
538     * Stop the audio track playback. This method blocks until the ongoing
539     * playback is stopped.
540     *
541     * @return The accurate current time when stop is effective expressed in
542     *         milliseconds
543     */
544    public long stopPlayback() {
545        final long stopTimeMs;
546        if (mPlaybackThread != null) {
547            stopTimeMs = mPlaybackThread.stopPlayback();
548            mPlaybackThread = null;
549        } else {
550            stopTimeMs = 0;
551        }
552        return stopTimeMs;
553    }
554
555    /**
556     * This API allows to generate a file containing the sample volume levels of
557     * this audio track object. This function may take significant time and is
558     * blocking. The filename can be retrieved using getAudioWaveformFilename().
559     *
560     * @param listener The progress listener
561     *
562     * @throws IOException if the output file cannot be created
563     * @throws IllegalArgumentException if the audio file does not have a valid
564     *             audio track
565     */
566    public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
567            throws IOException {
568        // TODO: Set mAudioWaveformFilename at the end once the extract is
569        // complete
570    }
571
572    /**
573     * Get the audio waveform file name if extractAudioWaveform was successful.
574     * The file format is as following:
575     * <ul>
576     * <li>first 4 bytes provide the number of samples for each value, as
577     * big-endian signed</li>
578     * <li>4 following bytes is the total number of values in the file, as
579     * big-endian signed</li>
580     * <li>then, all values follow as bytes</li>
581     * </ul>
582     *
583     * @return the name of the file, null if the file does not exist
584     */
585    public String getAudioWaveformFilename() {
586        return mAudioWaveformFilename;
587    }
588
589    /*
590     * {@inheritDoc}
591     */
592    @Override
593    public boolean equals(Object object) {
594        if (!(object instanceof AudioTrack)) {
595            return false;
596        }
597        return mUniqueId.equals(((AudioTrack)object).mUniqueId);
598    }
599
600    /*
601     * {@inheritDoc}
602     */
603    @Override
604    public int hashCode() {
605        return mUniqueId.hashCode();
606    }
607}
608