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.util.ArrayList;
     22 import java.util.List;
     23 
     24 import android.media.videoeditor.MediaArtistNativeHelper.AlphaMagicSettings;
     25 import android.media.videoeditor.MediaArtistNativeHelper.AudioTransition;
     26 import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings;
     27 import android.media.videoeditor.MediaArtistNativeHelper.EditSettings;
     28 import android.media.videoeditor.MediaArtistNativeHelper.EffectSettings;
     29 import android.media.videoeditor.MediaArtistNativeHelper.SlideTransitionSettings;
     30 import android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings;
     31 import android.media.videoeditor.MediaArtistNativeHelper.VideoTransition;
     32 
     33 /**
     34  * This class is super class for all transitions. Transitions (with the
     35  * exception of TransitionAtStart and TransitioAtEnd) can only be inserted
     36  * between media items.
     37  *
     38  * Adding a transition between MediaItems makes the
     39  * duration of the storyboard shorter by the duration of the Transition itself.
     40  * As a result, if the duration of the transition is larger than the smaller
     41  * duration of the two MediaItems associated with the Transition, an exception
     42  * will be thrown.
     43  *
     44  * During a transition, the audio track are cross-fading
     45  * automatically. {@hide}
     46  */
     47 public abstract class Transition {
     48     /**
     49      *  The transition behavior
     50      */
     51     private static final int BEHAVIOR_MIN_VALUE = 0;
     52 
     53     /** The transition starts slowly and speed up */
     54     public static final int BEHAVIOR_SPEED_UP = 0;
     55     /** The transition start fast and speed down */
     56     public static final int BEHAVIOR_SPEED_DOWN = 1;
     57     /** The transition speed is constant */
     58     public static final int BEHAVIOR_LINEAR = 2;
     59     /** The transition starts fast and ends fast with a slow middle */
     60     public static final int BEHAVIOR_MIDDLE_SLOW = 3;
     61     /** The transition starts slowly and ends slowly with a fast middle */
     62     public static final int BEHAVIOR_MIDDLE_FAST = 4;
     63 
     64     private static final int BEHAVIOR_MAX_VALUE = 4;
     65 
     66     /**
     67      *  The unique id of the transition
     68      */
     69     private final String mUniqueId;
     70 
     71     /**
     72      *  The transition is applied at the end of this media item
     73      */
     74     private final MediaItem mAfterMediaItem;
     75     /**
     76      *  The transition is applied at the beginning of this media item
     77      */
     78     private final MediaItem mBeforeMediaItem;
     79 
     80     /**
     81      *  The transition behavior
     82      */
     83     protected final int mBehavior;
     84 
     85     /**
     86      *  The transition duration
     87      */
     88     protected long mDurationMs;
     89 
     90     /**
     91      *  The transition filename
     92      */
     93     protected String mFilename;
     94 
     95     protected MediaArtistNativeHelper mNativeHelper;
     96     /**
     97      * An object of this type cannot be instantiated by using the default
     98      * constructor
     99      */
    100     @SuppressWarnings("unused")
    101     private Transition() {
    102         this(null, null, null, 0, 0);
    103     }
    104 
    105     /**
    106      * Constructor
    107      *
    108      * @param transitionId The transition id
    109      * @param afterMediaItem The transition is applied to the end of this
    110      *      media item
    111      * @param beforeMediaItem The transition is applied to the beginning of
    112      *      this media item
    113      * @param durationMs The duration of the transition in milliseconds
    114      * @param behavior The transition behavior
    115      */
    116     protected Transition(String transitionId, MediaItem afterMediaItem,
    117                          MediaItem beforeMediaItem,long durationMs,
    118                          int behavior) {
    119         if (behavior < BEHAVIOR_MIN_VALUE || behavior > BEHAVIOR_MAX_VALUE) {
    120             throw new IllegalArgumentException("Invalid behavior: " + behavior);
    121         }
    122         if ((afterMediaItem == null) && (beforeMediaItem == null)) {
    123             throw new IllegalArgumentException("Null media items");
    124         }
    125         mUniqueId = transitionId;
    126         mAfterMediaItem = afterMediaItem;
    127         mBeforeMediaItem = beforeMediaItem;
    128         mDurationMs = durationMs;
    129         mBehavior = behavior;
    130         mNativeHelper = null;
    131         if (durationMs > getMaximumDuration()) {
    132             throw new IllegalArgumentException("The duration is too large");
    133         }
    134         if (afterMediaItem != null) {
    135             mNativeHelper = afterMediaItem.getNativeContext();
    136         }else {
    137             mNativeHelper = beforeMediaItem.getNativeContext();
    138         }
    139     }
    140 
    141     /**
    142      * Get the ID of the transition.
    143      *
    144      * @return The ID of the transition
    145      */
    146     public String getId() {
    147         return mUniqueId;
    148     }
    149 
    150     /**
    151      * Get the media item at the end of which the transition is applied.
    152      *
    153      * @return The media item at the end of which the transition is applied
    154      */
    155     public MediaItem getAfterMediaItem() {
    156         return mAfterMediaItem;
    157     }
    158 
    159     /**
    160      * Get the media item at the beginning of which the transition is applied.
    161      *
    162      * @return The media item at the beginning of which the transition is
    163      *      applied
    164      */
    165     public MediaItem getBeforeMediaItem() {
    166         return mBeforeMediaItem;
    167     }
    168 
    169     /**
    170      * Set the duration of the transition.
    171      *
    172      * @param durationMs the duration of the transition in milliseconds
    173      */
    174     public void setDuration(long durationMs) {
    175         if (durationMs > getMaximumDuration()) {
    176             throw new IllegalArgumentException("The duration is too large");
    177         }
    178 
    179         mDurationMs = durationMs;
    180         invalidate();
    181         mNativeHelper.setGeneratePreview(true);
    182     }
    183 
    184     /**
    185      * Get the duration of the transition.
    186      *
    187      * @return the duration of the transition in milliseconds
    188      */
    189     public long getDuration() {
    190         return mDurationMs;
    191     }
    192 
    193     /**
    194      * The duration of a transition cannot be greater than half of the minimum
    195      * duration of the bounding media items.
    196      *
    197      * @return The maximum duration of this transition
    198      */
    199     public long getMaximumDuration() {
    200         if (mAfterMediaItem == null) {
    201             return mBeforeMediaItem.getTimelineDuration() / 2;
    202         } else if (mBeforeMediaItem == null) {
    203             return mAfterMediaItem.getTimelineDuration() / 2;
    204         } else {
    205             return (Math.min(mAfterMediaItem.getTimelineDuration(),
    206                     mBeforeMediaItem.getTimelineDuration()) / 2);
    207         }
    208     }
    209 
    210     /**
    211      * Get the behavior of the transition.
    212      *
    213      * @return The behavior
    214      */
    215     public int getBehavior() {
    216         return mBehavior;
    217     }
    218 
    219     /**
    220      * Get the transition data.
    221      *
    222      * @return The transition data in TransitionSettings object
    223      * {@link android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings}
    224      */
    225     TransitionSettings getTransitionSettings() {
    226         TransitionAlpha transitionAlpha = null;
    227         TransitionSliding transitionSliding = null;
    228         TransitionCrossfade transitionCrossfade = null;
    229         TransitionFadeBlack transitionFadeBlack = null;
    230         TransitionSettings transitionSetting = null;
    231         transitionSetting = new TransitionSettings();
    232         transitionSetting.duration = (int)getDuration();
    233         if (this instanceof TransitionAlpha) {
    234             transitionAlpha = (TransitionAlpha)this;
    235             transitionSetting.videoTransitionType = VideoTransition.ALPHA_MAGIC;
    236             transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
    237             transitionSetting.transitionBehaviour = mNativeHelper
    238             .getVideoTransitionBehaviour(transitionAlpha.getBehavior());
    239             transitionSetting.alphaSettings = new AlphaMagicSettings();
    240             transitionSetting.slideSettings = null;
    241             transitionSetting.alphaSettings.file = transitionAlpha.getPNGMaskFilename();
    242             transitionSetting.alphaSettings.blendingPercent = transitionAlpha.getBlendingPercent();
    243             transitionSetting.alphaSettings.invertRotation = transitionAlpha.isInvert();
    244             transitionSetting.alphaSettings.rgbWidth = transitionAlpha.getRGBFileWidth();
    245             transitionSetting.alphaSettings.rgbHeight = transitionAlpha.getRGBFileHeight();
    246 
    247         } else if (this instanceof TransitionSliding) {
    248             transitionSliding = (TransitionSliding)this;
    249             transitionSetting.videoTransitionType = VideoTransition.SLIDE_TRANSITION;
    250             transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
    251             transitionSetting.transitionBehaviour = mNativeHelper
    252             .getVideoTransitionBehaviour(transitionSliding.getBehavior());
    253             transitionSetting.alphaSettings = null;
    254             transitionSetting.slideSettings = new SlideTransitionSettings();
    255             transitionSetting.slideSettings.direction = mNativeHelper
    256             .getSlideSettingsDirection(transitionSliding.getDirection());
    257         } else if (this instanceof TransitionCrossfade) {
    258             transitionCrossfade = (TransitionCrossfade)this;
    259             transitionSetting.videoTransitionType = VideoTransition.CROSS_FADE;
    260             transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
    261             transitionSetting.transitionBehaviour = mNativeHelper
    262             .getVideoTransitionBehaviour(transitionCrossfade.getBehavior());
    263             transitionSetting.alphaSettings = null;
    264             transitionSetting.slideSettings = null;
    265         } else if (this instanceof TransitionFadeBlack) {
    266             transitionFadeBlack = (TransitionFadeBlack)this;
    267             transitionSetting.videoTransitionType = VideoTransition.FADE_BLACK;
    268             transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
    269             transitionSetting.transitionBehaviour = mNativeHelper
    270             .getVideoTransitionBehaviour(transitionFadeBlack.getBehavior());
    271             transitionSetting.alphaSettings = null;
    272             transitionSetting.slideSettings = null;
    273         }
    274 
    275         return transitionSetting;
    276     }
    277 
    278     /**
    279      * Checks if the effect and overlay applied on a media item
    280      * overlaps with the transition on media item.
    281      *
    282      * @param m The media item
    283      * @param clipSettings The ClipSettings object
    284      * @param clipNo The clip no.(out of the two media items
    285      * associated with current transition)for which the effect
    286      * clip should be generated
    287      * @return List of effects that overlap with the transition
    288      */
    289 
    290     List<EffectSettings>  isEffectandOverlayOverlapping(MediaItem m, ClipSettings clipSettings,
    291                                          int clipNo) {
    292         List<Effect> effects;
    293         List<Overlay> overlays;
    294         List<EffectSettings> effectSettings = new ArrayList<EffectSettings>();
    295         EffectSettings tmpEffectSettings;
    296 
    297         overlays = m.getAllOverlays();
    298         for (Overlay overlay : overlays) {
    299             tmpEffectSettings = mNativeHelper.getOverlaySettings((OverlayFrame)overlay);
    300             mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings,
    301                     clipSettings.beginCutTime, clipSettings.endCutTime);
    302             if (tmpEffectSettings.duration != 0) {
    303                 effectSettings.add(tmpEffectSettings);
    304             }
    305         }
    306 
    307         effects = m.getAllEffects();
    308         for (Effect effect : effects) {
    309             if (effect instanceof EffectColor) {
    310                 tmpEffectSettings = mNativeHelper.getEffectSettings((EffectColor)effect);
    311                 mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings,
    312                         clipSettings.beginCutTime, clipSettings.endCutTime);
    313                 if (tmpEffectSettings.duration != 0) {
    314                     if (m instanceof MediaVideoItem) {
    315                         tmpEffectSettings.fiftiesFrameRate = mNativeHelper
    316                         .GetClosestVideoFrameRate(((MediaVideoItem)m).getFps());
    317                     }
    318                     effectSettings.add(tmpEffectSettings);
    319                 }
    320             }
    321         }
    322 
    323          return effectSettings;
    324     }
    325 
    326     /**
    327      * Generate the video clip for the specified transition. This method may
    328      * block for a significant amount of time. Before the method completes
    329      * execution it sets the mFilename to the name of the newly generated
    330      * transition video clip file.
    331      */
    332     void generate() {
    333         MediaItem m1 = this.getAfterMediaItem();
    334         MediaItem m2 = this.getBeforeMediaItem();
    335         ClipSettings clipSettings1 = new ClipSettings();
    336         ClipSettings clipSettings2 = new ClipSettings();
    337         TransitionSettings transitionSetting = null;
    338         EditSettings editSettings = new EditSettings();
    339         List<EffectSettings> effectSettings_clip1;
    340         List<EffectSettings> effectSettings_clip2;
    341 
    342         String output = null;
    343 
    344         if (mNativeHelper == null) {
    345             if (m1 != null)
    346                 mNativeHelper = m1.getNativeContext();
    347             else if (m2 != null)
    348                 mNativeHelper = m2.getNativeContext();
    349         }
    350         transitionSetting = getTransitionSettings();
    351         if (m1 != null && m2 != null) {
    352             /* transition between media items */
    353             clipSettings1 = m1.getClipSettings();
    354             clipSettings2 = m2.getClipSettings();
    355             clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime -
    356                                                               this.mDurationMs);
    357             clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime +
    358                                                               this.mDurationMs);
    359             /*
    360              * Check how many effects and overlays overlap with transition and
    361              * generate effect clip first if there is any overlap
    362              */
    363             effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1);
    364             effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2);
    365             for (int index = 0; index < effectSettings_clip2.size(); index++ ) {
    366                 effectSettings_clip2.get(index).startTime += this.mDurationMs;
    367             }
    368             editSettings.effectSettingsArray =
    369                                               new EffectSettings[effectSettings_clip1.size()
    370                                                  + effectSettings_clip2.size()];
    371             int i=0,j=0;
    372             while (i < effectSettings_clip1.size()) {
    373                 editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i);
    374                 i++;
    375                 j++;
    376             }
    377             i=0;
    378             while (i < effectSettings_clip2.size()) {
    379                 editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i);
    380                 i++;
    381                 j++;
    382             }
    383         } else if (m1 == null && m2 != null) {
    384             /* begin transition at first media item */
    385             m2.generateBlankFrame(clipSettings1);
    386             clipSettings2 = m2.getClipSettings();
    387             clipSettings1.endCutTime = (int)(this.mDurationMs + 50);
    388             clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime +
    389                                                               this.mDurationMs);
    390             /*
    391              * Check how many effects and overlays overlap with transition and
    392              * generate effect clip first if there is any overlap
    393              */
    394             effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2);
    395             for (int index = 0; index < effectSettings_clip2.size(); index++ ) {
    396                 effectSettings_clip2.get(index).startTime += this.mDurationMs;
    397             }
    398             editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip2.size()];
    399             int i=0, j=0;
    400             while (i < effectSettings_clip2.size()) {
    401                 editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i);
    402                 i++;
    403                 j++;
    404             }
    405         } else if (m1 != null && m2 == null) {
    406             /* end transition at last media item */
    407             clipSettings1 = m1.getClipSettings();
    408             m1.generateBlankFrame(clipSettings2);
    409             clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime -
    410                                                               this.mDurationMs);
    411             clipSettings2.endCutTime = (int)(this.mDurationMs + 50);
    412             /*
    413              * Check how many effects and overlays overlap with transition and
    414              * generate effect clip first if there is any overlap
    415              */
    416             effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1);
    417             editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip1.size()];
    418             int i=0,j=0;
    419             while (i < effectSettings_clip1.size()) {
    420                 editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i);
    421                 i++;
    422                 j++;
    423             }
    424         }
    425 
    426         editSettings.clipSettingsArray = new ClipSettings[2];
    427         editSettings.clipSettingsArray[0] = clipSettings1;
    428         editSettings.clipSettingsArray[1] = clipSettings2;
    429         editSettings.backgroundMusicSettings = null;
    430         editSettings.transitionSettingsArray = new TransitionSettings[1];
    431         editSettings.transitionSettingsArray[0] = transitionSetting;
    432         output = mNativeHelper.generateTransitionClip(editSettings, mUniqueId,
    433                                                       m1, m2,this);
    434         setFilename(output);
    435     }
    436 
    437 
    438     /**
    439      * Set the transition filename.
    440      */
    441     void setFilename(String filename) {
    442         mFilename = filename;
    443     }
    444 
    445     /**
    446      * Get the transition filename.
    447      */
    448     String getFilename() {
    449         return mFilename;
    450     }
    451 
    452     /**
    453      * Remove any resources associated with this transition
    454      */
    455     void invalidate() {
    456         if (mFilename != null) {
    457             new File(mFilename).delete();
    458             mFilename = null;
    459         }
    460     }
    461 
    462     /**
    463      * Check if the transition is generated.
    464      *
    465      * @return true if the transition is generated
    466      */
    467     boolean isGenerated() {
    468         return (mFilename != null);
    469     }
    470 
    471     /*
    472      * {@inheritDoc}
    473      */
    474     @Override
    475     public boolean equals(Object object) {
    476         if (!(object instanceof Transition)) {
    477             return false;
    478         }
    479         return mUniqueId.equals(((Transition)object).mUniqueId);
    480     }
    481 
    482     /*
    483      * {@inheritDoc}
    484      */
    485     @Override
    486     public int hashCode() {
    487         return mUniqueId.hashCode();
    488     }
    489 }
    490