AudioTrack.java revision 9bcedf7cf3e9c981837f2d8ec98cd118efad3f01
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
17
18package android.media.videoeditor;
19
20import java.io.File;
21import java.io.IOException;
22import java.lang.ref.SoftReference;
23
24import android.media.videoeditor.MediaArtistNativeHelper.Properties;
25
26/**
27 * This class allows to handle an audio track. This audio file is mixed with the
28 * audio samples of the media items.
29 * {@hide}
30 */
31public class AudioTrack {
32
33    /**
34     *  Instance variables
35     *  Private object for calling native methods via MediaArtistNativeHelper
36     */
37    private final MediaArtistNativeHelper mMANativeHelper;
38    private final String mUniqueId;
39    private final String mFilename;
40    private long mStartTimeMs;
41    private long mTimelineDurationMs;
42    private int mVolumePercent;
43    private long mBeginBoundaryTimeMs;
44    private long mEndBoundaryTimeMs;
45    private boolean mLoop;
46    private boolean mMuted;
47    private final long mDurationMs;
48    private final int mAudioChannels;
49    private final int mAudioType;
50    private final int mAudioBitrate;
51    private final int mAudioSamplingFrequency;
52
53    /**
54     *  Ducking variables
55     */
56    private int mDuckingThreshold;
57    private int mDuckedTrackVolume;
58    private boolean mIsDuckingEnabled;
59
60    /**
61     *  The audio waveform filename
62     */
63    private String mAudioWaveformFilename;
64
65    /**
66     *  The audio waveform data
67     */
68    private SoftReference<WaveformData> mWaveformData;
69
70    /**
71     * An object of this type cannot be instantiated by using the default
72     * constructor
73     */
74    @SuppressWarnings("unused")
75    private AudioTrack() throws IOException {
76        this(null, null, null);
77    }
78
79    /**
80     * Constructor
81     *
82     * @param editor The video editor reference
83     * @param audioTrackId The audio track id
84     * @param filename The absolute file name
85     *
86     * @throws IOException if file is not found
87     * @throws IllegalArgumentException if file format is not supported or if
88     *         the codec is not supported or if editor is not of type
89     *         VideoEditorImpl.
90     */
91    public AudioTrack(VideoEditor editor, String audioTrackId, String filename) throws IOException {
92        this(editor, audioTrackId, filename, 0, 0, MediaItem.END_OF_FILE, false, 100, false, false,
93                0, 0, null);
94    }
95
96    /**
97     * Constructor
98     *
99     * @param editor The video editor reference
100     * @param audioTrackId The audio track id
101     * @param filename The audio filename. In case file contains Audio and Video,
102     *         only the Audio stream will be used as Audio Track.
103     * @param startTimeMs the start time in milliseconds (relative to the
104     *         timeline)
105     * @param beginMs start time in the audio track in milliseconds (relative to
106     *         the beginning of the audio track)
107     * @param endMs end time in the audio track in milliseconds (relative to the
108     *         beginning of the audio track)
109     * @param loop true to loop the audio track
110     * @param volume The volume in percentage
111     * @param muted true if the audio track is muted
112     * @param threshold Ducking will be activated when the relative energy in
113     *         the media items audio signal goes above this value. The valid
114     *         range of values is 0 to 90.
115     * @param duckedTrackVolume The relative volume of the audio track when
116     *         ducking is active. The valid range of values is 0 to 100.
117     * @param audioWaveformFilename The name of the waveform file
118     *
119     * @throws IOException if file is not found
120     * @throws IllegalArgumentException if file format is not supported or if
121     *             the codec is not supported or if editor is not of type
122     *             VideoEditorImpl.
123     */
124    AudioTrack(VideoEditor editor, String audioTrackId, String filename,
125               long startTimeMs,long beginMs, long endMs, boolean loop,
126               int volume, boolean muted,boolean duckingEnabled,
127               int duckThreshold, int duckedTrackVolume,
128            String audioWaveformFilename) throws IOException {
129        Properties properties = null;
130        File file = new File(filename);
131        if (!file.exists()) {
132            throw new IOException(filename + " not found ! ");
133        }
134
135        if (editor instanceof VideoEditorImpl) {
136            mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext();
137        } else {
138            throw new IllegalArgumentException("editor is not of type VideoEditorImpl");
139        }
140        try {
141          properties = mMANativeHelper.getMediaProperties(filename);
142        } catch (Exception e) {
143            throw new IllegalArgumentException("Unsupported file or file not found");
144        }
145        switch (mMANativeHelper.getFileType(properties.fileType)) {
146            case MediaProperties.FILE_3GP:
147            case MediaProperties.FILE_MP4:
148            case MediaProperties.FILE_MP3:
149                break;
150
151            default: {
152                throw new IllegalArgumentException("Unsupported input file type");
153            }
154        }
155        switch (mMANativeHelper.getAudioCodecType(properties.audioFormat)) {
156            case MediaProperties.ACODEC_AMRNB:
157            case MediaProperties.ACODEC_AMRWB:
158            case MediaProperties.ACODEC_AAC_LC:
159            case MediaProperties.ACODEC_MP3:
160                break;
161            default:
162                throw new IllegalArgumentException("Unsupported Audio Codec Format in Input File");
163        }
164
165        if (endMs == MediaItem.END_OF_FILE) {
166            endMs = properties.audioDuration;
167        }
168
169        mUniqueId = audioTrackId;
170        mFilename = filename;
171        mStartTimeMs = startTimeMs;
172        mDurationMs = properties.audioDuration;
173        mAudioChannels = properties.audioChannels;
174        mAudioBitrate = properties.audioBitrate;
175        mAudioSamplingFrequency = properties.audioSamplingFrequency;
176        mAudioType = properties.audioFormat;
177        mTimelineDurationMs = endMs - beginMs;
178        mVolumePercent = volume;
179
180        mBeginBoundaryTimeMs = beginMs;
181        mEndBoundaryTimeMs = endMs;
182
183        mLoop = loop;
184        mMuted = muted;
185        mIsDuckingEnabled = duckingEnabled;
186        mDuckingThreshold = duckThreshold;
187        mDuckedTrackVolume = duckedTrackVolume;
188
189        mAudioWaveformFilename = audioWaveformFilename;
190        if (audioWaveformFilename != null) {
191            mWaveformData =
192                new SoftReference<WaveformData>(new WaveformData(audioWaveformFilename));
193        } else {
194            mWaveformData = null;
195        }
196    }
197
198    /**
199     * Get the id of the audio track
200     *
201     * @return The id of the audio track
202     */
203    public String getId() {
204        return mUniqueId;
205    }
206
207    /**
208     * Get the filename for this audio track source.
209     *
210     * @return The filename as an absolute file name
211     */
212    public String getFilename() {
213        return mFilename;
214    }
215
216    /**
217     * Get the number of audio channels in the source of this audio track
218     *
219     * @return The number of audio channels in the source of this audio track
220     */
221    public int getAudioChannels() {
222        return mAudioChannels;
223    }
224
225    /**
226     * Get the audio codec of the source of this audio track
227     *
228     * @return The audio codec of the source of this audio track
229     * {@link android.media.videoeditor.MediaProperties}
230     */
231    public int getAudioType() {
232        return mAudioType;
233    }
234
235    /**
236     * Get the audio sample frequency of the audio track
237     *
238     * @return The audio sample frequency of the audio track
239     */
240    public int getAudioSamplingFrequency() {
241        return mAudioSamplingFrequency;
242    }
243
244    /**
245     * Get the audio bitrate of the audio track
246     *
247     * @return The audio bitrate of the audio track
248     */
249    public int getAudioBitrate() {
250        return mAudioBitrate;
251    }
252
253    /**
254     * Set the volume of this audio track as percentage of the volume in the
255     * original audio source file.
256     *
257     * @param volumePercent Percentage of the volume to apply. If it is set to
258     *         0, then volume becomes mute. It it is set to 100, then volume
259     *         is same as original volume. It it is set to 200, then volume
260     *         is doubled (provided that volume amplification is supported)
261     *
262     * @throws UnsupportedOperationException if volume amplification is
263     *         requested and is not supported.
264     */
265    public void setVolume(int volumePercent) {
266        if (volumePercent > MediaProperties.AUDIO_MAX_VOLUME_PERCENT) {
267            throw new IllegalArgumentException("Volume set exceeds maximum allowed value");
268        }
269
270         if (volumePercent < 0) {
271            throw new IllegalArgumentException("Invalid Volume ");
272        }
273        mVolumePercent = volumePercent;
274        /**
275         *  Force update of preview settings
276         */
277        mMANativeHelper.setGeneratePreview(true);
278    }
279
280    /**
281     * Get the volume of the audio track as percentage of the volume in the
282     * original audio source file.
283     *
284     * @return The volume in percentage
285     */
286    public int getVolume() {
287        return mVolumePercent;
288    }
289
290    /**
291     * Mute/Unmute the audio track
292     *
293     * @param muted true to mute the audio track. SetMute(true) will make
294     *         the volume of this Audio Track to 0.
295     */
296    public void setMute(boolean muted) {
297        mMuted = muted;
298        /**
299         *  Force update of preview settings
300         */
301        mMANativeHelper.setGeneratePreview(true);
302    }
303
304    /**
305     * Check if the audio track is muted
306     *
307     * @return true if the audio track is muted
308     */
309    public boolean isMuted() {
310        return mMuted;
311    }
312
313    /**
314     * Get the start time of this audio track relative to the storyboard
315     * timeline.
316     *
317     * @return The start time in milliseconds
318     */
319
320    public long getStartTime() {
321        return mStartTimeMs;
322    }
323
324    /**
325     * Get the audio track duration
326     *
327     * @return The duration in milliseconds. This value represents actual audio
328     *         track duration. This value is not effected by 'enableLoop' or
329     *         'setExtractBoundaries'.
330     */
331    public long getDuration() {
332        return mDurationMs;
333    }
334
335    /**
336     * Get the audio track timeline duration
337     *
338     * @return The timeline duration as defined by the begin and end boundaries
339     */
340    public long getTimelineDuration() {
341        return mTimelineDurationMs;
342    }
343
344    /**
345     * Sets the start and end marks for trimming an audio track
346     *
347     * @param beginMs start time in the audio track in milliseconds (relative to
348     *         the beginning of the audio track)
349     * @param endMs end time in the audio track in milliseconds (relative to the
350     *         beginning of the audio track)
351     */
352    public void setExtractBoundaries(long beginMs, long endMs) {
353        if (beginMs > mDurationMs) {
354            throw new IllegalArgumentException("Invalid start time");
355        }
356        if (endMs > mDurationMs) {
357            throw new IllegalArgumentException("Invalid end time");
358        }
359        if (beginMs < 0) {
360            throw new IllegalArgumentException("Invalid start time; is < 0");
361        }
362        if (endMs < 0) {
363            throw new IllegalArgumentException("Invalid end time; is < 0");
364        }
365
366        mBeginBoundaryTimeMs = beginMs;
367        mEndBoundaryTimeMs = endMs;
368
369        mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
370        /**
371         *  Force update of preview settings
372         */
373        mMANativeHelper.setGeneratePreview(true);
374    }
375
376    /**
377     * Get the boundary begin time
378     *
379     * @return The boundary begin time
380     */
381    public long getBoundaryBeginTime() {
382        return mBeginBoundaryTimeMs;
383    }
384
385    /**
386     * Get the boundary end time
387     *
388     * @return The boundary end time
389     */
390    public long getBoundaryEndTime() {
391        return mEndBoundaryTimeMs;
392    }
393
394    /**
395     * Enable the loop mode for this audio track. Note that only one of the
396     * audio tracks in the timeline can have the loop mode enabled. When looping
397     * is enabled the samples between mBeginBoundaryTimeMs and
398     * mEndBoundaryTimeMs are looped.
399     */
400    public void enableLoop() {
401        if (!mLoop) {
402            mLoop = true;
403            /**
404             *  Force update of preview settings
405             */
406            mMANativeHelper.setGeneratePreview(true);
407        }
408    }
409
410    /**
411     * Disable the loop mode
412     */
413    public void disableLoop() {
414        if (mLoop) {
415            mLoop = false;
416            /**
417             *  Force update of preview settings
418             */
419            mMANativeHelper.setGeneratePreview(true);
420        }
421    }
422
423    /**
424     * Check if looping is enabled
425     *
426     * @return true if looping is enabled
427     */
428    public boolean isLooping() {
429        return mLoop;
430    }
431
432    /**
433     * Disable the audio duck effect
434     */
435    public void disableDucking() {
436        if (mIsDuckingEnabled) {
437            mIsDuckingEnabled = false;
438            /**
439             *  Force update of preview settings
440             */
441            mMANativeHelper.setGeneratePreview(true);
442        }
443    }
444
445    /**
446     * Enable ducking by specifying the required parameters
447     *
448     * @param threshold Ducking will be activated when the energy in
449     *         the media items audio signal goes above this value. The valid
450     *         range of values is 0db to 90dB. 0dB is equivalent to disabling
451     *         ducking.
452     * @param duckedTrackVolume The relative volume of the audio track when ducking
453     *         is active. The valid range of values is 0 to 100.
454     */
455    public void enableDucking(int threshold, int duckedTrackVolume) {
456        if (threshold < 0 || threshold > 90) {
457            throw new IllegalArgumentException("Invalid threshold value: " + threshold);
458        }
459
460        if (duckedTrackVolume < 0 || duckedTrackVolume > 100) {
461            throw new IllegalArgumentException("Invalid duckedTrackVolume value: "
462                    + duckedTrackVolume);
463        }
464
465        mDuckingThreshold = threshold;
466        mDuckedTrackVolume = duckedTrackVolume;
467        mIsDuckingEnabled = true;
468        /**
469         *  Force update of preview settings
470         */
471        mMANativeHelper.setGeneratePreview(true);
472    }
473
474    /**
475     * Check if ducking is enabled
476     *
477     * @return true if ducking is enabled
478     */
479    public boolean isDuckingEnabled() {
480        return mIsDuckingEnabled;
481    }
482
483    /**
484     * Get the ducking threshold.
485     *
486     * @return The ducking threshold
487     */
488    public int getDuckingThreshhold() {
489        return mDuckingThreshold;
490    }
491
492    /**
493     * Get the ducked track volume.
494     *
495     * @return The ducked track volume
496     */
497    public int getDuckedTrackVolume() {
498        return mDuckedTrackVolume;
499    }
500
501    /**
502     * This API allows to generate a file containing the sample volume levels of
503     * this audio track object. This function may take significant time and is
504     * blocking. The filename can be retrieved using getAudioWaveformFilename().
505     *
506     * @param listener The progress listener
507     *
508     * @throws IOException if the output file cannot be created
509     * @throws IllegalArgumentException if the audio file does not have a valid
510     *         audio track
511     * @throws IllegalStateException if the codec type is unsupported
512     */
513    public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
514    throws IOException {
515        if (mAudioWaveformFilename == null) {
516            /**
517             *  AudioWaveformFilename is generated
518             */
519            final String projectPath = mMANativeHelper.getProjectPath();
520            final String audioWaveFilename = String.format(projectPath + "/audioWaveformFile-"
521                    + getId() + ".dat");
522
523            /**
524             * Logic to get frame duration = (no. of frames per sample * 1000)/
525             * sampling frequency
526             */
527            final int frameDuration;
528            final int sampleCount;
529            final int codecType = mMANativeHelper.getAudioCodecType(mAudioType);
530            switch (codecType) {
531                case MediaProperties.ACODEC_AMRNB: {
532                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB * 1000)
533                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
534                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB;
535                    break;
536                }
537
538                case MediaProperties.ACODEC_AMRWB: {
539                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000)
540                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
541                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB;
542                    break;
543                }
544
545                case MediaProperties.ACODEC_AAC_LC: {
546                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000)
547                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
548                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC;
549                    break;
550                }
551
552                case MediaProperties.ACODEC_MP3: {
553                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_MP3 * 1000)
554                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
555                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_MP3;
556                    break;
557                }
558
559                default: {
560                    throw new IllegalStateException("Unsupported codec type: "
561                                                                   + codecType);
562                }
563            }
564
565            mMANativeHelper.generateAudioGraph( mUniqueId,
566                    mFilename,
567                    audioWaveFilename,
568                    frameDuration,
569                    MediaProperties.DEFAULT_CHANNEL_COUNT,
570                    sampleCount,
571                    listener,
572                    false);
573            /**
574             *  Record the generated file name
575             */
576            mAudioWaveformFilename = audioWaveFilename;
577        }
578        mWaveformData = new SoftReference<WaveformData>(new WaveformData(mAudioWaveformFilename));
579    }
580
581    /**
582     * Get the audio waveform file name if extractAudioWaveform was successful.
583     *
584     * @return the name of the file, null if the file does not exist
585     */
586    String getAudioWaveformFilename() {
587        return mAudioWaveformFilename;
588    }
589
590    /**
591     * Delete the waveform file
592     */
593    void invalidate() {
594        if (mAudioWaveformFilename != null) {
595            new File(mAudioWaveformFilename).delete();
596            mAudioWaveformFilename = null;
597            mWaveformData = null;
598        }
599    }
600
601    /**
602     * Get the audio waveform data.
603     *
604     * @return The waveform data
605     *
606     * @throws IOException if the waveform file cannot be found
607     */
608    public WaveformData getWaveformData() throws IOException {
609        if (mWaveformData == null) {
610            return null;
611        }
612
613        WaveformData waveformData = mWaveformData.get();
614        if (waveformData != null) {
615            return waveformData;
616        } else if (mAudioWaveformFilename != null) {
617            try {
618                waveformData = new WaveformData(mAudioWaveformFilename);
619            } catch (IOException e) {
620                throw e;
621            }
622            mWaveformData = new SoftReference<WaveformData>(waveformData);
623            return waveformData;
624        } else {
625            return null;
626        }
627    }
628
629    /*
630     * {@inheritDoc}
631     */
632    @Override
633    public boolean equals(Object object) {
634        if (!(object instanceof AudioTrack)) {
635            return false;
636        }
637        return mUniqueId.equals(((AudioTrack)object).mUniqueId);
638    }
639
640    /*
641     * {@inheritDoc}
642     */
643    @Override
644    public int hashCode() {
645        return mUniqueId.hashCode();
646    }
647}
648