Home | History | Annotate | Download | only in videoeditor
      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 
     18 package android.media.videoeditor;
     19 
     20 import java.io.File;
     21 import java.io.IOException;
     22 import java.lang.ref.SoftReference;
     23 
     24 import 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  */
     31 public 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