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