AudioTrack.java revision a573b563b3c6a3edc60393543dc9adb7ade4f188
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
274        /**
275         *  Force update of preview settings
276         */
277        mMANativeHelper.setGeneratePreview(true);
278
279        mVolumePercent = volumePercent;
280    }
281
282    /**
283     * Get the volume of the audio track as percentage of the volume in the
284     * original audio source file.
285     *
286     * @return The volume in percentage
287     */
288    public int getVolume() {
289        return mVolumePercent;
290    }
291
292    /**
293     * Mute/Unmute the audio track
294     *
295     * @param muted true to mute the audio track. SetMute(true) will make
296     *         the volume of this Audio Track to 0.
297     */
298    public void setMute(boolean muted) {
299        /**
300         *  Force update of preview settings
301         */
302        mMANativeHelper.setGeneratePreview(true);
303        mMuted = muted;
304    }
305
306    /**
307     * Check if the audio track is muted
308     *
309     * @return true if the audio track is muted
310     */
311    public boolean isMuted() {
312        return mMuted;
313    }
314
315    /**
316     * Get the start time of this audio track relative to the storyboard
317     * timeline.
318     *
319     * @return The start time in milliseconds
320     */
321
322    public long getStartTime() {
323        return mStartTimeMs;
324    }
325
326    /**
327     * Get the audio track duration
328     *
329     * @return The duration in milliseconds. This value represents actual audio
330     *         track duration. This value is not effected by 'enableLoop' or
331     *         'setExtractBoundaries'.
332     */
333    public long getDuration() {
334        return mDurationMs;
335    }
336
337    /**
338     * Get the audio track timeline duration
339     *
340     * @return The timeline duration as defined by the begin and end boundaries
341     */
342    public long getTimelineDuration() {
343        return mTimelineDurationMs;
344    }
345
346    /**
347     * Sets the start and end marks for trimming an audio track
348     *
349     * @param beginMs start time in the audio track in milliseconds (relative to
350     *         the beginning of the audio track)
351     * @param endMs end time in the audio track in milliseconds (relative to the
352     *         beginning of the audio track)
353     */
354    public void setExtractBoundaries(long beginMs, long endMs) {
355        if (beginMs > mDurationMs) {
356            throw new IllegalArgumentException("Invalid start time");
357        }
358        if (endMs > mDurationMs) {
359            throw new IllegalArgumentException("Invalid end time");
360        }
361        if (beginMs < 0) {
362            throw new IllegalArgumentException("Invalid start time; is < 0");
363        }
364        if (endMs < 0) {
365            throw new IllegalArgumentException("Invalid end time; is < 0");
366        }
367
368        /**
369         *  Force update of preview settings
370         */
371        mMANativeHelper.setGeneratePreview(true);
372
373        mBeginBoundaryTimeMs = beginMs;
374        mEndBoundaryTimeMs = endMs;
375
376        mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
377    }
378
379    /**
380     * Get the boundary begin time
381     *
382     * @return The boundary begin time
383     */
384    public long getBoundaryBeginTime() {
385        return mBeginBoundaryTimeMs;
386    }
387
388    /**
389     * Get the boundary end time
390     *
391     * @return The boundary end time
392     */
393    public long getBoundaryEndTime() {
394        return mEndBoundaryTimeMs;
395    }
396
397    /**
398     * Enable the loop mode for this audio track. Note that only one of the
399     * audio tracks in the timeline can have the loop mode enabled. When looping
400     * is enabled the samples between mBeginBoundaryTimeMs and
401     * mEndBoundaryTimeMs are looped.
402     */
403    public void enableLoop() {
404        if (!mLoop) {
405            /**
406             *  Force update of preview settings
407             */
408            mMANativeHelper.setGeneratePreview(true);
409            mLoop = true;
410        }
411    }
412
413    /**
414     * Disable the loop mode
415     */
416    public void disableLoop() {
417        if (mLoop) {
418            /**
419             *  Force update of preview settings
420             */
421            mMANativeHelper.setGeneratePreview(true);
422            mLoop = false;
423        }
424    }
425
426    /**
427     * Check if looping is enabled
428     *
429     * @return true if looping is enabled
430     */
431    public boolean isLooping() {
432        return mLoop;
433    }
434
435    /**
436     * Disable the audio duck effect
437     */
438    public void disableDucking() {
439        if (mIsDuckingEnabled) {
440            /**
441             *  Force update of preview settings
442             */
443            mMANativeHelper.setGeneratePreview(true);
444            mIsDuckingEnabled = false;
445        }
446    }
447
448    /**
449     * Enable ducking by specifying the required parameters
450     *
451     * @param threshold Ducking will be activated when the energy in
452     *         the media items audio signal goes above this value. The valid
453     *         range of values is 0db to 90dB. 0dB is equivalent to disabling
454     *         ducking.
455     * @param duckedTrackVolume The relative volume of the audio track when ducking
456     *         is active. The valid range of values is 0 to 100.
457     */
458    public void enableDucking(int threshold, int duckedTrackVolume) {
459        if (threshold < 0 || threshold > 90) {
460            throw new IllegalArgumentException("Invalid threshold value: " + threshold);
461        }
462
463        if (duckedTrackVolume < 0 || duckedTrackVolume > 100) {
464            throw new IllegalArgumentException("Invalid duckedTrackVolume value: "
465                    + duckedTrackVolume);
466        }
467
468        /**
469         *  Force update of preview settings
470         */
471        mMANativeHelper.setGeneratePreview(true);
472
473        mDuckingThreshold = threshold;
474        mDuckedTrackVolume = duckedTrackVolume;
475        mIsDuckingEnabled = true;
476    }
477
478    /**
479     * Check if ducking is enabled
480     *
481     * @return true if ducking is enabled
482     */
483    public boolean isDuckingEnabled() {
484        return mIsDuckingEnabled;
485    }
486
487    /**
488     * Get the ducking threshold.
489     *
490     * @return The ducking threshold
491     */
492    public int getDuckingThreshhold() {
493        return mDuckingThreshold;
494    }
495
496    /**
497     * Get the ducked track volume.
498     *
499     * @return The ducked track volume
500     */
501    public int getDuckedTrackVolume() {
502        return mDuckedTrackVolume;
503    }
504
505    /**
506     * This API allows to generate a file containing the sample volume levels of
507     * this audio track object. This function may take significant time and is
508     * blocking. The filename can be retrieved using getAudioWaveformFilename().
509     *
510     * @param listener The progress listener
511     *
512     * @throws IOException if the output file cannot be created
513     * @throws IllegalArgumentException if the audio file does not have a valid
514     *         audio track
515     * @throws IllegalStateException if the codec type is unsupported
516     */
517    public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
518    throws IOException {
519        if (mAudioWaveformFilename == null) {
520            /**
521             *  AudioWaveformFilename is generated
522             */
523            final String projectPath = mMANativeHelper.getProjectPath();
524            final String audioWaveFilename = String.format(projectPath + "/audioWaveformFile-"
525                    + getId() + ".dat");
526
527            /**
528             * Logic to get frame duration = (no. of frames per sample * 1000)/
529             * sampling frequency
530             */
531            final int frameDuration;
532            final int sampleCount;
533            final int codecType = mMANativeHelper.getAudioCodecType(mAudioType);
534            switch (codecType) {
535                case MediaProperties.ACODEC_AMRNB: {
536                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB * 1000)
537                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
538                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB;
539                    break;
540                }
541
542                case MediaProperties.ACODEC_AMRWB: {
543                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000)
544                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
545                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB;
546                    break;
547                }
548
549                case MediaProperties.ACODEC_AAC_LC: {
550                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000)
551                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
552                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC;
553                    break;
554                }
555
556                case MediaProperties.ACODEC_MP3: {
557                    frameDuration = (MediaProperties.SAMPLES_PER_FRAME_MP3 * 1000)
558                    / MediaProperties.DEFAULT_SAMPLING_FREQUENCY;
559                    sampleCount = MediaProperties.SAMPLES_PER_FRAME_MP3;
560                    break;
561                }
562
563                default: {
564                    throw new IllegalStateException("Unsupported codec type: "
565                                                                   + codecType);
566                }
567            }
568
569            mMANativeHelper.generateAudioGraph( mUniqueId,
570                    mFilename,
571                    audioWaveFilename,
572                    frameDuration,
573                    MediaProperties.DEFAULT_CHANNEL_COUNT,
574                    sampleCount,
575                    listener,
576                    false);
577            /**
578             *  Record the generated file name
579             */
580            mAudioWaveformFilename = audioWaveFilename;
581        }
582        mWaveformData = new SoftReference<WaveformData>(new WaveformData(mAudioWaveformFilename));
583    }
584
585    /**
586     * Get the audio waveform file name if extractAudioWaveform was successful.
587     *
588     * @return the name of the file, null if the file does not exist
589     */
590    String getAudioWaveformFilename() {
591        return mAudioWaveformFilename;
592    }
593
594    /**
595     * Delete the waveform file
596     */
597    void invalidate() {
598        if (mAudioWaveformFilename != null) {
599            new File(mAudioWaveformFilename).delete();
600            mAudioWaveformFilename = null;
601            mWaveformData = null;
602        }
603    }
604
605    /**
606     * Get the audio waveform data.
607     *
608     * @return The waveform data
609     *
610     * @throws IOException if the waveform file cannot be found
611     */
612    public WaveformData getWaveformData() throws IOException {
613        if (mWaveformData == null) {
614            return null;
615        }
616
617        WaveformData waveformData = mWaveformData.get();
618        if (waveformData != null) {
619            return waveformData;
620        } else if (mAudioWaveformFilename != null) {
621            try {
622                waveformData = new WaveformData(mAudioWaveformFilename);
623            } catch (IOException e) {
624                throw e;
625            }
626            mWaveformData = new SoftReference<WaveformData>(waveformData);
627            return waveformData;
628        } else {
629            return null;
630        }
631    }
632
633    /*
634     * {@inheritDoc}
635     */
636    @Override
637    public boolean equals(Object object) {
638        if (!(object instanceof AudioTrack)) {
639            return false;
640        }
641        return mUniqueId.equals(((AudioTrack)object).mUniqueId);
642    }
643
644    /*
645     * {@inheritDoc}
646     */
647    @Override
648    public int hashCode() {
649        return mUniqueId.hashCode();
650    }
651}
652