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