Transition.java revision 9bcedf7cf3e9c981837f2d8ec98cd118efad3f01
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.util.ArrayList;
22import java.util.List;
23
24import android.media.videoeditor.MediaArtistNativeHelper.AlphaMagicSettings;
25import android.media.videoeditor.MediaArtistNativeHelper.AudioTransition;
26import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings;
27import android.media.videoeditor.MediaArtistNativeHelper.EditSettings;
28import android.media.videoeditor.MediaArtistNativeHelper.EffectSettings;
29import android.media.videoeditor.MediaArtistNativeHelper.SlideTransitionSettings;
30import android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings;
31import 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 */
47public 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    }
135
136    /**
137     * Get the ID of the transition.
138     *
139     * @return The ID of the transition
140     */
141    public String getId() {
142        return mUniqueId;
143    }
144
145    /**
146     * Get the media item at the end of which the transition is applied.
147     *
148     * @return The media item at the end of which the transition is applied
149     */
150    public MediaItem getAfterMediaItem() {
151        return mAfterMediaItem;
152    }
153
154    /**
155     * Get the media item at the beginning of which the transition is applied.
156     *
157     * @return The media item at the beginning of which the transition is
158     *      applied
159     */
160    public MediaItem getBeforeMediaItem() {
161        return mBeforeMediaItem;
162    }
163
164    /**
165     * Set the duration of the transition.
166     *
167     * @param durationMs the duration of the transition in milliseconds
168     */
169    public void setDuration(long durationMs) {
170        if (durationMs > getMaximumDuration()) {
171            throw new IllegalArgumentException("The duration is too large");
172        }
173
174        mDurationMs = durationMs;
175        invalidate();
176    }
177
178    /**
179     * Get the duration of the transition.
180     *
181     * @return the duration of the transition in milliseconds
182     */
183    public long getDuration() {
184        return mDurationMs;
185    }
186
187    /**
188     * The duration of a transition cannot be greater than half of the minimum
189     * duration of the bounding media items.
190     *
191     * @return The maximum duration of this transition
192     */
193    public long getMaximumDuration() {
194        if (mAfterMediaItem == null) {
195            return mBeforeMediaItem.getTimelineDuration() / 2;
196        } else if (mBeforeMediaItem == null) {
197            return mAfterMediaItem.getTimelineDuration() / 2;
198        } else {
199            return (Math.min(mAfterMediaItem.getTimelineDuration(),
200                    mBeforeMediaItem.getTimelineDuration()) / 2);
201        }
202    }
203
204    /**
205     * Get the behavior of the transition.
206     *
207     * @return The behavior
208     */
209    public int getBehavior() {
210        return mBehavior;
211    }
212
213    /**
214     * Get the transition data.
215     *
216     * @return The transition data in TransitionSettings object
217     * {@link android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings}
218     */
219    TransitionSettings getTransitionSettings() {
220        TransitionAlpha transitionAlpha = null;
221        TransitionSliding transitionSliding = null;
222        TransitionCrossfade transitionCrossfade = null;
223        TransitionFadeBlack transitionFadeBlack = null;
224        TransitionSettings transitionSetting = null;
225        transitionSetting = new TransitionSettings();
226        transitionSetting.duration = (int)getDuration();
227        if (this instanceof TransitionAlpha) {
228            transitionAlpha = (TransitionAlpha)this;
229            transitionSetting.videoTransitionType = VideoTransition.ALPHA_MAGIC;
230            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
231            transitionSetting.transitionBehaviour = mNativeHelper
232            .getVideoTransitionBehaviour(transitionAlpha.getBehavior());
233            transitionSetting.alphaSettings = new AlphaMagicSettings();
234            transitionSetting.slideSettings = null;
235            transitionSetting.alphaSettings.file = transitionAlpha.getPNGMaskFilename();
236            transitionSetting.alphaSettings.blendingPercent = transitionAlpha.getBlendingPercent();
237            transitionSetting.alphaSettings.invertRotation = transitionAlpha.isInvert();
238            transitionSetting.alphaSettings.rgbWidth = transitionAlpha.getRGBFileWidth();
239            transitionSetting.alphaSettings.rgbHeight = transitionAlpha.getRGBFileHeight();
240
241        } else if (this instanceof TransitionSliding) {
242            transitionSliding = (TransitionSliding)this;
243            transitionSetting.videoTransitionType = VideoTransition.SLIDE_TRANSITION;
244            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
245            transitionSetting.transitionBehaviour = mNativeHelper
246            .getVideoTransitionBehaviour(transitionSliding.getBehavior());
247            transitionSetting.alphaSettings = null;
248            transitionSetting.slideSettings = new SlideTransitionSettings();
249            transitionSetting.slideSettings.direction = mNativeHelper
250            .getSlideSettingsDirection(transitionSliding.getDirection());
251        } else if (this instanceof TransitionCrossfade) {
252            transitionCrossfade = (TransitionCrossfade)this;
253            transitionSetting.videoTransitionType = VideoTransition.CROSS_FADE;
254            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
255            transitionSetting.transitionBehaviour = mNativeHelper
256            .getVideoTransitionBehaviour(transitionCrossfade.getBehavior());
257            transitionSetting.alphaSettings = null;
258            transitionSetting.slideSettings = null;
259        } else if (this instanceof TransitionFadeBlack) {
260            transitionFadeBlack = (TransitionFadeBlack)this;
261            transitionSetting.videoTransitionType = VideoTransition.FADE_BLACK;
262            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
263            transitionSetting.transitionBehaviour = mNativeHelper
264            .getVideoTransitionBehaviour(transitionFadeBlack.getBehavior());
265            transitionSetting.alphaSettings = null;
266            transitionSetting.slideSettings = null;
267        }
268
269        return transitionSetting;
270    }
271
272    /**
273     * Checks if the effect and overlay applied on a media item
274     * overlaps with the transition on media item.
275     *
276     * @param m The media item
277     * @param clipSettings The ClipSettings object
278     * @param clipNo The clip no.(out of the two media items
279     * associated with current transition)for which the effect
280     * clip should be generated
281     * @return List of effects that overlap with the transition
282     */
283
284    List<EffectSettings>  isEffectandOverlayOverlapping(MediaItem m, ClipSettings clipSettings,
285                                         int clipNo) {
286        List<Effect> effects;
287        List<Overlay> overlays;
288        List<EffectSettings> effectSettings = new ArrayList<EffectSettings>();
289        EffectSettings tmpEffectSettings;
290
291        effects = m.getAllEffects();
292        for (Effect effect : effects) {
293            if (effect instanceof EffectColor) {
294                tmpEffectSettings = mNativeHelper.getEffectSettings((EffectColor)effect);
295                mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings,
296                        clipSettings.beginCutTime, clipSettings.endCutTime);
297                if (tmpEffectSettings.duration != 0) {
298                    if (m instanceof MediaVideoItem) {
299                        tmpEffectSettings.fiftiesFrameRate = mNativeHelper
300                        .GetClosestVideoFrameRate(((MediaVideoItem)m).getFps());
301                    }
302                    effectSettings.add(tmpEffectSettings);
303                }
304            }
305        }
306        overlays = m.getAllOverlays();
307        for (Overlay overlay : overlays) {
308            tmpEffectSettings = mNativeHelper.getOverlaySettings((OverlayFrame)overlay);
309            mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings,
310                    clipSettings.beginCutTime, clipSettings.endCutTime);
311            if (tmpEffectSettings.duration != 0) {
312                effectSettings.add(tmpEffectSettings);
313            }
314        }
315         return effectSettings;
316    }
317
318    /**
319     * Generate the video clip for the specified transition. This method may
320     * block for a significant amount of time. Before the method completes
321     * execution it sets the mFilename to the name of the newly generated
322     * transition video clip file.
323     */
324    void generate() {
325        MediaItem m1 = this.getAfterMediaItem();
326        MediaItem m2 = this.getBeforeMediaItem();
327        ClipSettings clipSettings1 = new ClipSettings();
328        ClipSettings clipSettings2 = new ClipSettings();
329        TransitionSettings transitionSetting = null;
330        EditSettings editSettings = new EditSettings();
331        List<EffectSettings> effectSettings_clip1;
332        List<EffectSettings> effectSettings_clip2;
333
334        String output = null;
335        String effectClip1 = null;
336        String effectClip2 = null;
337
338        if (mNativeHelper == null) {
339            if (m1 != null)
340                mNativeHelper = m1.getNativeContext();
341            else if (m2 != null)
342                mNativeHelper = m2.getNativeContext();
343        }
344        transitionSetting = getTransitionSettings();
345        if (m1 != null && m2 != null) {
346            /* transition between media items */
347            clipSettings1 = m1.getClipSettings();
348            clipSettings2 = m2.getClipSettings();
349            clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime -
350                                                              this.mDurationMs);
351            clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime +
352                                                              this.mDurationMs);
353            /*
354             * Check how many effects and overlays overlap with transition and
355             * generate effect clip first if there is any overlap
356             */
357            effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1);
358            effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2);
359            for (int index = 0; index < effectSettings_clip2.size(); index++ ) {
360                effectSettings_clip2.get(index).startTime += this.mDurationMs;
361            }
362            editSettings.effectSettingsArray =
363                                              new EffectSettings[effectSettings_clip1.size()
364                                                 + effectSettings_clip2.size()];
365            int i=0,j=0;
366            while (i < effectSettings_clip1.size()) {
367                editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i);
368                i++;
369                j++;
370            }
371            i=0;
372            while (i < effectSettings_clip2.size()) {
373                editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i);
374                i++;
375                j++;
376            }
377        } else if (m1 == null && m2 != null) {
378            /* begin transition at first media item */
379            m2.generateBlankFrame(clipSettings1);
380            clipSettings2 = m2.getClipSettings();
381            clipSettings1.endCutTime = (int)(this.mDurationMs + 50);
382            clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime +
383                                                              this.mDurationMs);
384            /*
385             * Check how many effects and overlays overlap with transition and
386             * generate effect clip first if there is any overlap
387             */
388            effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2);
389            for (int index = 0; index < effectSettings_clip2.size(); index++ ) {
390                effectSettings_clip2.get(index).startTime += this.mDurationMs;
391            }
392            editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip2.size()];
393            int i=0, j=0;
394            while (i < effectSettings_clip2.size()) {
395                editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i);
396                i++;
397                j++;
398            }
399        } else if (m1 != null && m2 == null) {
400            /* end transition at last media item */
401            clipSettings1 = m1.getClipSettings();
402            m1.generateBlankFrame(clipSettings2);
403            clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime -
404                                                              this.mDurationMs);
405            clipSettings2.endCutTime = (int)(this.mDurationMs + 50);
406            /*
407             * Check how many effects and overlays overlap with transition and
408             * generate effect clip first if there is any overlap
409             */
410            effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1);
411            editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip1.size()];
412            int i=0,j=0;
413            while (i < effectSettings_clip1.size()) {
414                editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i);
415                i++;
416                j++;
417            }
418        }
419
420        editSettings.clipSettingsArray = new ClipSettings[2];
421        editSettings.clipSettingsArray[0] = clipSettings1;
422        editSettings.clipSettingsArray[1] = clipSettings2;
423        editSettings.backgroundMusicSettings = null;
424        editSettings.transitionSettingsArray = new TransitionSettings[1];
425        editSettings.transitionSettingsArray[0] = transitionSetting;
426        output = mNativeHelper.generateTransitionClip(editSettings, mUniqueId,
427                                                      m1, m2,this);
428        setFilename(output);
429    }
430
431
432    /**
433     * Set the transition filename.
434     */
435    void setFilename(String filename) {
436        mFilename = filename;
437    }
438
439    /**
440     * Get the transition filename.
441     */
442    String getFilename() {
443        return mFilename;
444    }
445
446    /**
447     * Remove any resources associated with this transition
448     */
449    void invalidate() {
450        if (mFilename != null) {
451            new File(mFilename).delete();
452            mFilename = null;
453        }
454    }
455
456    /**
457     * Check if the transition is generated.
458     *
459     * @return true if the transition is generated
460     */
461    boolean isGenerated() {
462        return (mFilename != null);
463    }
464
465    /*
466     * {@inheritDoc}
467     */
468    @Override
469    public boolean equals(Object object) {
470        if (!(object instanceof Transition)) {
471            return false;
472        }
473        return mUniqueId.equals(((Transition)object).mUniqueId);
474    }
475
476    /*
477     * {@inheritDoc}
478     */
479    @Override
480    public int hashCode() {
481        return mUniqueId.hashCode();
482    }
483}
484