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