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