VideoEditorImpl.java revision f67441eb370f764710b5697885d1a1549d41e819
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.FileInputStream; 22import java.io.FileNotFoundException; 23import java.io.FileOutputStream; 24import java.io.IOException; 25import java.io.StringWriter; 26import java.util.ArrayList; 27import java.util.Iterator; 28import java.util.List; 29import java.util.Map; 30import java.util.concurrent.Semaphore; 31import java.util.concurrent.TimeUnit; 32 33import org.xmlpull.v1.XmlPullParser; 34import org.xmlpull.v1.XmlPullParserException; 35import org.xmlpull.v1.XmlSerializer; 36 37import android.graphics.Bitmap; 38import android.graphics.Rect; 39import android.media.videoeditor.MediaImageItem; 40import android.media.videoeditor.MediaItem; 41import android.util.Log; 42import android.util.Xml; 43import android.view.Surface; 44import android.view.SurfaceHolder; 45import android.os.Debug; 46import android.os.SystemProperties; 47import android.os.Environment; 48 49/** 50 * The VideoEditor implementation {@hide} 51 */ 52public class VideoEditorImpl implements VideoEditor { 53 /* 54 * Logging 55 */ 56 private static final String TAG = "VideoEditorImpl"; 57 58 /* 59 * The project filename 60 */ 61 private static final String PROJECT_FILENAME = "videoeditor.xml"; 62 63 /* 64 * XML tags 65 */ 66 private static final String TAG_PROJECT = "project"; 67 private static final String TAG_MEDIA_ITEMS = "media_items"; 68 private static final String TAG_MEDIA_ITEM = "media_item"; 69 private static final String TAG_TRANSITIONS = "transitions"; 70 private static final String TAG_TRANSITION = "transition"; 71 private static final String TAG_OVERLAYS = "overlays"; 72 private static final String TAG_OVERLAY = "overlay"; 73 private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes"; 74 private static final String TAG_EFFECTS = "effects"; 75 private static final String TAG_EFFECT = "effect"; 76 private static final String TAG_AUDIO_TRACKS = "audio_tracks"; 77 private static final String TAG_AUDIO_TRACK = "audio_track"; 78 79 private static final String ATTR_ID = "id"; 80 private static final String ATTR_FILENAME = "filename"; 81 private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "waveform"; 82 private static final String ATTR_RENDERING_MODE = "rendering_mode"; 83 private static final String ATTR_ASPECT_RATIO = "aspect_ratio"; 84 private static final String ATTR_REGENERATE_PCM = "regeneratePCMFlag"; 85 private static final String ATTR_TYPE = "type"; 86 private static final String ATTR_DURATION = "duration"; 87 private static final String ATTR_START_TIME = "start_time"; 88 private static final String ATTR_BEGIN_TIME = "begin_time"; 89 private static final String ATTR_END_TIME = "end_time"; 90 private static final String ATTR_VOLUME = "volume"; 91 private static final String ATTR_BEHAVIOR = "behavior"; 92 private static final String ATTR_DIRECTION = "direction"; 93 private static final String ATTR_BLENDING = "blending"; 94 private static final String ATTR_INVERT = "invert"; 95 private static final String ATTR_MASK = "mask"; 96 private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item"; 97 private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item"; 98 private static final String ATTR_COLOR_EFFECT_TYPE = "color_type"; 99 private static final String ATTR_COLOR_EFFECT_VALUE = "color_value"; 100 private static final String ATTR_START_RECT_LEFT = "start_l"; 101 private static final String ATTR_START_RECT_TOP = "start_t"; 102 private static final String ATTR_START_RECT_RIGHT = "start_r"; 103 private static final String ATTR_START_RECT_BOTTOM = "start_b"; 104 private static final String ATTR_END_RECT_LEFT = "end_l"; 105 private static final String ATTR_END_RECT_TOP = "end_t"; 106 private static final String ATTR_END_RECT_RIGHT = "end_r"; 107 private static final String ATTR_END_RECT_BOTTOM = "end_b"; 108 private static final String ATTR_LOOP = "loop"; 109 private static final String ATTR_MUTED = "muted"; 110 private static final String ATTR_DUCK_ENABLED = "ducking_enabled"; 111 private static final String ATTR_DUCK_THRESHOLD = "ducking_threshold"; 112 private static final String ATTR_DUCKED_TRACK_VOLUME = "ducking_volume"; 113 private static final String ATTR_GENERATED_IMAGE_CLIP = "generated_image_clip"; 114 private static final String ATTR_IS_IMAGE_CLIP_GENERATED = "is_image_clip_generated"; 115 private static final String ATTR_GENERATED_TRANSITION_CLIP = "generated_transition_clip"; 116 private static final String ATTR_IS_TRANSITION_GENERATED = "is_transition_generated"; 117 private static final String ATTR_OVERLAY_RGB_FILENAME = "overlay_rgb_filename"; 118 private static final String ATTR_OVERLAY_FRAME_WIDTH = "overlay_frame_width"; 119 private static final String ATTR_OVERLAY_FRAME_HEIGHT = "overlay_frame_height"; 120 private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH = "resized_RGBframe_width"; 121 private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT = "resized_RGBframe_height"; 122 123 private static final int ENGINE_ACCESS_MAX_TIMEOUT_MS = 500; 124 /* 125 * Instance variables 126 */ 127 private final Semaphore mLock; 128 private final String mProjectPath; 129 private final List<MediaItem> mMediaItems = new ArrayList<MediaItem>(); 130 private final List<AudioTrack> mAudioTracks = new ArrayList<AudioTrack>(); 131 private final List<Transition> mTransitions = new ArrayList<Transition>(); 132 private long mDurationMs; 133 private int mAspectRatio; 134 135 /* 136 * Private Object for calling native Methods via MediaArtistNativeHelper 137 */ 138 private MediaArtistNativeHelper mMANativeHelper; 139 private boolean mPreviewInProgress = false; 140 private final boolean mMallocDebug; 141 142 /** 143 * Constructor 144 * 145 * @param projectPath - The path where the VideoEditor stores all files 146 * related to the project 147 */ 148 public VideoEditorImpl(String projectPath) throws IOException { 149 String s; 150 s = SystemProperties.get("libc.debug.malloc"); 151 if (s.equals("1")) { 152 mMallocDebug = true; 153 try { 154 dumpHeap("HeapAtStart"); 155 } catch (Exception ex) { 156 Log.e(TAG, "dumpHeap returned error in constructor"); 157 } 158 } else { 159 mMallocDebug = false; 160 } 161 mLock = new Semaphore(1, true); 162 mMANativeHelper = new MediaArtistNativeHelper(projectPath, mLock, this); 163 mProjectPath = projectPath; 164 final File projectXml = new File(projectPath, PROJECT_FILENAME); 165 if (projectXml.exists()) { 166 try { 167 load(); 168 } catch (Exception ex) { 169 ex.printStackTrace(); 170 throw new IOException(ex.toString()); 171 } 172 } else { 173 mAspectRatio = MediaProperties.ASPECT_RATIO_16_9; 174 mDurationMs = 0; 175 } 176 } 177 178 /* 179 * @return The MediaArtistNativeHelper object 180 */ 181 MediaArtistNativeHelper getNativeContext() { 182 return mMANativeHelper; 183 } 184 185 /* 186 * {@inheritDoc} 187 */ 188 public synchronized void addAudioTrack(AudioTrack audioTrack) { 189 if (audioTrack == null) { 190 throw new IllegalArgumentException("Audio Track is null"); 191 } 192 193 if (mAudioTracks.size() == 1) { 194 throw new IllegalArgumentException("No more tracks can be added"); 195 } 196 197 mMANativeHelper.setGeneratePreview(true); 198 199 /* 200 * Add the audio track to AudioTrack list 201 */ 202 mAudioTracks.add(audioTrack); 203 204 /* 205 * Form the audio PCM file path 206 */ 207 final String audioTrackPCMFilePath = String.format(mProjectPath + "/" 208 + "AudioPcm" + audioTrack.getId() + ".pcm"); 209 210 /* 211 * Create PCM only if not generated in previous session 212 */ 213 if (new File(audioTrackPCMFilePath).exists()) { 214 mMANativeHelper.setAudioflag(false); 215 } 216 217 } 218 219 /* 220 * {@inheritDoc} 221 */ 222 public synchronized void addMediaItem(MediaItem mediaItem) { 223 /* 224 * Validate Media Item 225 */ 226 if (mediaItem == null) { 227 throw new IllegalArgumentException("Media item is null"); 228 } 229 /* 230 * Add the Media item to MediaItem list 231 */ 232 if (mMediaItems.contains(mediaItem)) { 233 throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId()); 234 } 235 236 mMANativeHelper.setGeneratePreview(true); 237 238 /* 239 * Invalidate the end transition if necessary 240 */ 241 final int mediaItemsCount = mMediaItems.size(); 242 if (mediaItemsCount > 0) { 243 removeTransitionAfter(mediaItemsCount - 1); 244 } 245 246 /* 247 * Add the new media item 248 */ 249 mMediaItems.add(mediaItem); 250 251 computeTimelineDuration(); 252 253 /* 254 * Generate project thumbnail only from first media Item on storyboard 255 */ 256 if (mMediaItems.size() == 1) { 257 generateProjectThumbnail(); 258 } 259 } 260 261 262 /* 263 * {@inheritDoc} 264 */ 265 public synchronized void addTransition(Transition transition) { 266 if (transition == null) { 267 throw new IllegalArgumentException("Null Transition"); 268 } 269 270 final MediaItem beforeMediaItem = transition.getBeforeMediaItem(); 271 final MediaItem afterMediaItem = transition.getAfterMediaItem(); 272 /* 273 * Check if the MediaItems are in sequence 274 */ 275 if (mMediaItems == null) { 276 throw new IllegalArgumentException("No media items are added"); 277 } 278 279 if ((afterMediaItem != null) && (beforeMediaItem != null)) { 280 final int afterMediaItemIndex = mMediaItems.indexOf(afterMediaItem); 281 final int beforeMediaItemIndex = mMediaItems.indexOf(beforeMediaItem); 282 283 if ((afterMediaItemIndex == -1) || (beforeMediaItemIndex == -1)) { 284 throw new IllegalArgumentException 285 ("Either of the mediaItem is not found in the list"); 286 } 287 288 if (afterMediaItemIndex != (beforeMediaItemIndex - 1) ) { 289 throw new IllegalArgumentException("MediaItems are not in sequence"); 290 } 291 } 292 293 mMANativeHelper.setGeneratePreview(true); 294 295 mTransitions.add(transition); 296 /* 297 * Cross reference the transitions 298 */ 299 if (afterMediaItem != null) { 300 /* 301 * If a transition already exists at the specified position then 302 * invalidate it. 303 */ 304 if (afterMediaItem.getEndTransition() != null) { 305 afterMediaItem.getEndTransition().invalidate(); 306 mTransitions.remove(afterMediaItem.getEndTransition()); 307 } 308 afterMediaItem.setEndTransition(transition); 309 } 310 311 if (beforeMediaItem != null) { 312 /* 313 * If a transition already exists at the specified position then 314 * invalidate it. 315 */ 316 if (beforeMediaItem.getBeginTransition() != null) { 317 beforeMediaItem.getBeginTransition().invalidate(); 318 mTransitions.remove(beforeMediaItem.getBeginTransition()); 319 } 320 beforeMediaItem.setBeginTransition(transition); 321 } 322 323 computeTimelineDuration(); 324 } 325 326 /* 327 * {@inheritDoc} 328 */ 329 public void cancelExport(String filename) { 330 if (mMANativeHelper != null && filename != null) { 331 mMANativeHelper.stop(filename); 332 } 333 } 334 335 /* 336 * {@inheritDoc} 337 */ 338 public void export(String filename, int height, int bitrate, 339 int audioCodec, int videoCodec, 340 ExportProgressListener listener) throws IOException { 341 342 switch (audioCodec) { 343 case MediaProperties.ACODEC_AAC_LC: 344 break; 345 case MediaProperties.ACODEC_AMRNB: 346 break; 347 348 default: { 349 String message = "Unsupported audio codec type " + audioCodec; 350 throw new IllegalArgumentException(message); 351 } 352 } 353 354 switch (videoCodec) { 355 case MediaProperties.VCODEC_H263: 356 break; 357 case MediaProperties.VCODEC_H264BP: 358 break; 359 case MediaProperties.VCODEC_MPEG4: 360 break; 361 362 default: { 363 String message = "Unsupported video codec type " + videoCodec; 364 throw new IllegalArgumentException(message); 365 } 366 } 367 368 export(filename, height, bitrate, listener); 369 } 370 371 /* 372 * {@inheritDoc} 373 */ 374 public void export(String filename, int height, int bitrate, 375 ExportProgressListener listener) throws IOException { 376 if (filename == null) { 377 throw new IllegalArgumentException("export: filename is null"); 378 } 379 380 final File tempPathFile = new File(filename); 381 if (tempPathFile == null) { 382 throw new IOException(filename + "can not be created"); 383 } 384 385 if (mMediaItems.size() == 0) { 386 throw new IllegalStateException("No MediaItems added"); 387 } 388 389 switch (height) { 390 case MediaProperties.HEIGHT_144: 391 break; 392 case MediaProperties.HEIGHT_360: 393 break; 394 case MediaProperties.HEIGHT_480: 395 break; 396 case MediaProperties.HEIGHT_720: 397 break; 398 399 default: { 400 String message = "Unsupported height value " + height; 401 throw new IllegalArgumentException(message); 402 } 403 } 404 405 switch (bitrate) { 406 case MediaProperties.BITRATE_28K: 407 break; 408 case MediaProperties.BITRATE_40K: 409 break; 410 case MediaProperties.BITRATE_64K: 411 break; 412 case MediaProperties.BITRATE_96K: 413 break; 414 case MediaProperties.BITRATE_128K: 415 break; 416 case MediaProperties.BITRATE_192K: 417 break; 418 case MediaProperties.BITRATE_256K: 419 break; 420 case MediaProperties.BITRATE_384K: 421 break; 422 case MediaProperties.BITRATE_512K: 423 break; 424 case MediaProperties.BITRATE_800K: 425 break; 426 case MediaProperties.BITRATE_2M: 427 break; 428 case MediaProperties.BITRATE_5M: 429 break; 430 case MediaProperties.BITRATE_8M: 431 break; 432 433 default: { 434 final String message = "Unsupported bitrate value " + bitrate; 435 throw new IllegalArgumentException(message); 436 } 437 } 438 439 boolean semAcquireDone = false; 440 try { 441 lock(); 442 semAcquireDone = true; 443 444 if (mMANativeHelper == null) { 445 throw new IllegalStateException("The video editor is not initialized"); 446 } 447 448 mMANativeHelper.export(filename, mProjectPath, height,bitrate, 449 mMediaItems, mTransitions, mAudioTracks, listener); 450 } catch (InterruptedException ex) { 451 Log.e(TAG, "Sem acquire NOT successful in export"); 452 } finally { 453 if (semAcquireDone) { 454 unlock(); 455 } 456 } 457 } 458 459 /* 460 * {@inheritDoc} 461 */ 462 public void generatePreview(MediaProcessingProgressListener listener) { 463 boolean semAcquireDone = false; 464 try { 465 lock(); 466 semAcquireDone = true; 467 468 if (mMANativeHelper == null) { 469 throw new IllegalStateException("The video editor is not initialized"); 470 } 471 472 if ((mMediaItems.size() > 0) || (mAudioTracks.size() > 0)) { 473 mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, mAudioTracks, 474 listener); 475 } 476 } catch (InterruptedException ex) { 477 Log.e(TAG, "Sem acquire NOT successful in previewStoryBoard"); 478 } finally { 479 if (semAcquireDone) { 480 unlock(); 481 } 482 } 483 } 484 485 /* 486 * {@inheritDoc} 487 */ 488 public List<AudioTrack> getAllAudioTracks() { 489 return mAudioTracks; 490 } 491 492 /* 493 * {@inheritDoc} 494 */ 495 public List<MediaItem> getAllMediaItems() { 496 return mMediaItems; 497 } 498 499 /* 500 * {@inheritDoc} 501 */ 502 public List<Transition> getAllTransitions() { 503 return mTransitions; 504 } 505 506 /* 507 * {@inheritDoc} 508 */ 509 public int getAspectRatio() { 510 return mAspectRatio; 511 } 512 513 /* 514 * {@inheritDoc} 515 */ 516 public AudioTrack getAudioTrack(String audioTrackId) { 517 for (AudioTrack at : mAudioTracks) { 518 if (at.getId().equals(audioTrackId)) { 519 return at; 520 } 521 } 522 return null; 523 } 524 525 /* 526 * {@inheritDoc} 527 */ 528 public long getDuration() { 529 /** 530 * Since MediaImageItem can change duration we need to compute the 531 * duration here 532 */ 533 computeTimelineDuration(); 534 return mDurationMs; 535 } 536 537 /* 538 * Force updates the timeline duration 539 */ 540 void updateTimelineDuration() { 541 computeTimelineDuration(); 542 } 543 544 /* 545 * {@inheritDoc} 546 */ 547 public synchronized MediaItem getMediaItem(String mediaItemId) { 548 for (MediaItem mediaItem : mMediaItems) { 549 if (mediaItem.getId().equals(mediaItemId)) { 550 return mediaItem; 551 } 552 } 553 return null; 554 } 555 556 /* 557 * {@inheritDoc} 558 */ 559 public String getPath() { 560 return mProjectPath; 561 } 562 563 /* 564 * {@inheritDoc} 565 */ 566 public Transition getTransition(String transitionId) { 567 for (Transition transition : mTransitions) { 568 if (transition.getId().equals(transitionId)) { 569 return transition; 570 } 571 } 572 return null; 573 } 574 575 /* 576 * {@inheritDoc} 577 */ 578 public synchronized void insertAudioTrack(AudioTrack audioTrack, 579 String afterAudioTrackId) { 580 if (mAudioTracks.size() == 1) { 581 throw new IllegalArgumentException("No more tracks can be added"); 582 } 583 584 if (afterAudioTrackId == null) { 585 mMANativeHelper.setGeneratePreview(true); 586 mAudioTracks.add(0, audioTrack); 587 } else { 588 final int audioTrackCount = mAudioTracks.size(); 589 for (int i = 0; i < audioTrackCount; i++) { 590 AudioTrack at = mAudioTracks.get(i); 591 if (at.getId().equals(afterAudioTrackId)) { 592 mMANativeHelper.setGeneratePreview(true); 593 mAudioTracks.add(i + 1, audioTrack); 594 return; 595 } 596 } 597 598 throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId); 599 } 600 } 601 602 /* 603 * {@inheritDoc} 604 */ 605 public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) { 606 if (mMediaItems.contains(mediaItem)) { 607 throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId()); 608 } 609 610 if (afterMediaItemId == null) { 611 mMANativeHelper.setGeneratePreview(true); 612 if (mMediaItems.size() > 0) { 613 /** 614 * Invalidate the transition at the beginning of the timeline 615 */ 616 removeTransitionBefore(0); 617 } 618 619 mMediaItems.add(0, mediaItem); 620 computeTimelineDuration(); 621 generateProjectThumbnail(); 622 } else { 623 final int mediaItemCount = mMediaItems.size(); 624 for (int i = 0; i < mediaItemCount; i++) { 625 final MediaItem mi = mMediaItems.get(i); 626 if (mi.getId().equals(afterMediaItemId)) { 627 mMANativeHelper.setGeneratePreview(true); 628 /** 629 * Invalidate the transition at this position 630 */ 631 removeTransitionAfter(i); 632 /** 633 * Insert the new media item 634 */ 635 mMediaItems.add(i + 1, mediaItem); 636 computeTimelineDuration(); 637 return; 638 } 639 } 640 641 throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId); 642 } 643 } 644 645 /* 646 * {@inheritDoc} 647 */ 648 public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) { 649 throw new IllegalStateException("Not supported"); 650 } 651 652 /* 653 * {@inheritDoc} 654 */ 655 public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) { 656 final MediaItem moveMediaItem = removeMediaItem(mediaItemId,true); 657 if (moveMediaItem == null) { 658 throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId); 659 } 660 661 if (afterMediaItemId == null) { 662 if (mMediaItems.size() > 0) { 663 mMANativeHelper.setGeneratePreview(true); 664 665 /** 666 * Invalidate adjacent transitions at the insertion point 667 */ 668 removeTransitionBefore(0); 669 670 /** 671 * Insert the media item at the new position 672 */ 673 mMediaItems.add(0, moveMediaItem); 674 computeTimelineDuration(); 675 676 generateProjectThumbnail(); 677 } else { 678 throw new IllegalStateException("Cannot move media item (it is the only item)"); 679 } 680 } else { 681 final int mediaItemCount = mMediaItems.size(); 682 for (int i = 0; i < mediaItemCount; i++) { 683 final MediaItem mi = mMediaItems.get(i); 684 if (mi.getId().equals(afterMediaItemId)) { 685 mMANativeHelper.setGeneratePreview(true); 686 /** 687 * Invalidate adjacent transitions at the insertion point 688 */ 689 removeTransitionAfter(i); 690 /** 691 * Insert the media item at the new position 692 */ 693 mMediaItems.add(i + 1, moveMediaItem); 694 computeTimelineDuration(); 695 return; 696 } 697 } 698 699 throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId); 700 } 701 } 702 703 /* 704 * {@inheritDoc} 705 */ 706 public void release() { 707 stopPreview(); 708 709 boolean semAcquireDone = false; 710 try { 711 lock(); 712 semAcquireDone = true; 713 714 if (mMANativeHelper != null) { 715 mMediaItems.clear(); 716 mAudioTracks.clear(); 717 mTransitions.clear(); 718 mMANativeHelper.releaseNativeHelper(); 719 mMANativeHelper = null; 720 } 721 } catch (Exception ex) { 722 Log.e(TAG, "Sem acquire NOT successful in export", ex); 723 } finally { 724 if (semAcquireDone) { 725 unlock(); 726 } 727 } 728 if (mMallocDebug) { 729 try { 730 dumpHeap("HeapAtEnd"); 731 } catch (Exception ex) { 732 Log.e(TAG, "dumpHeap returned error in release"); 733 } 734 } 735 } 736 737 /* 738 * {@inheritDoc} 739 */ 740 public synchronized void removeAllMediaItems() { 741 mMANativeHelper.setGeneratePreview(true); 742 743 mMediaItems.clear(); 744 745 /** 746 * Invalidate all transitions 747 */ 748 for (Transition transition : mTransitions) { 749 transition.invalidate(); 750 } 751 mTransitions.clear(); 752 753 mDurationMs = 0; 754 /** 755 * If a thumbnail already exists, then delete it 756 */ 757 if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) { 758 (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete(); 759 } 760 761 } 762 763 /* 764 * {@inheritDoc} 765 */ 766 public synchronized AudioTrack removeAudioTrack(String audioTrackId) { 767 final AudioTrack audioTrack = getAudioTrack(audioTrackId); 768 if (audioTrack != null) { 769 mMANativeHelper.setGeneratePreview(true); 770 mAudioTracks.remove(audioTrack); 771 audioTrack.invalidate(); 772 mMANativeHelper.invalidatePcmFile(); 773 mMANativeHelper.setAudioflag(true); 774 } else { 775 throw new IllegalArgumentException(" No more audio tracks"); 776 } 777 return audioTrack; 778 } 779 780 /* 781 * {@inheritDoc} 782 */ 783 public synchronized MediaItem removeMediaItem(String mediaItemId) { 784 final String firstItemString = mMediaItems.get(0).getId(); 785 final MediaItem mediaItem = getMediaItem(mediaItemId); 786 if (mediaItem != null) { 787 mMANativeHelper.setGeneratePreview(true); 788 /** 789 * Remove the media item 790 */ 791 mMediaItems.remove(mediaItem); 792 if (mediaItem instanceof MediaImageItem) { 793 ((MediaImageItem)mediaItem).invalidate(); 794 } 795 final List<Overlay> overlays = mediaItem.getAllOverlays(); 796 if (overlays.size() > 0) { 797 for (Overlay overlay : overlays) { 798 if (overlay instanceof OverlayFrame) { 799 final OverlayFrame overlayFrame = (OverlayFrame)overlay; 800 overlayFrame.invalidate(); 801 } 802 } 803 } 804 805 /** 806 * Remove the adjacent transitions 807 */ 808 removeAdjacentTransitions(mediaItem); 809 computeTimelineDuration(); 810 } 811 812 /** 813 * If string equals first mediaItem, then 814 * generate Project thumbnail 815 */ 816 if (firstItemString.equals(mediaItemId)) { 817 generateProjectThumbnail(); 818 } 819 820 if (mediaItem instanceof MediaVideoItem) { 821 /** 822 * Delete the graph file 823 */ 824 ((MediaVideoItem)mediaItem).invalidate(); 825 } 826 return mediaItem; 827 } 828 829 private synchronized MediaItem removeMediaItem(String mediaItemId, boolean flag) { 830 final String firstItemString = mMediaItems.get(0).getId(); 831 832 final MediaItem mediaItem = getMediaItem(mediaItemId); 833 if (mediaItem != null) { 834 mMANativeHelper.setGeneratePreview(true); 835 /** 836 * Remove the media item 837 */ 838 mMediaItems.remove(mediaItem); 839 /** 840 * Remove the adjacent transitions 841 */ 842 removeAdjacentTransitions(mediaItem); 843 computeTimelineDuration(); 844 } 845 846 /** 847 * If string equals first mediaItem, then 848 * generate Project thumbail 849 */ 850 if (firstItemString.equals(mediaItemId)) { 851 generateProjectThumbnail(); 852 } 853 return mediaItem; 854 } 855 856 /* 857 * {@inheritDoc} 858 */ 859 public synchronized Transition removeTransition(String transitionId) { 860 final Transition transition = getTransition(transitionId); 861 if (transition == null) { 862 throw new IllegalStateException("Transition not found: " + transitionId); 863 } 864 865 mMANativeHelper.setGeneratePreview(true); 866 867 /** 868 * Remove the transition references 869 */ 870 final MediaItem afterMediaItem = transition.getAfterMediaItem(); 871 if (afterMediaItem != null) { 872 afterMediaItem.setEndTransition(null); 873 } 874 875 final MediaItem beforeMediaItem = transition.getBeforeMediaItem(); 876 if (beforeMediaItem != null) { 877 beforeMediaItem.setBeginTransition(null); 878 } 879 880 mTransitions.remove(transition); 881 transition.invalidate(); 882 computeTimelineDuration(); 883 return transition; 884 } 885 886 /* 887 * {@inheritDoc} 888 */ 889 public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs, 890 OverlayData overlayData) { 891 if (surfaceHolder == null) { 892 throw new IllegalArgumentException("Surface Holder is null"); 893 } 894 895 final Surface surface = surfaceHolder.getSurface(); 896 if (surface == null) { 897 throw new IllegalArgumentException("Surface could not be retrieved from Surface holder"); 898 } 899 900 if (timeMs < 0) { 901 throw new IllegalArgumentException("requested time not correct"); 902 } else if (timeMs > mDurationMs) { 903 throw new IllegalArgumentException("requested time more than duration"); 904 } 905 long result = 0; 906 907 boolean semAcquireDone = false; 908 try { 909 semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS); 910 if (semAcquireDone == false) { 911 throw new IllegalStateException("Timeout waiting for semaphore"); 912 } 913 914 if (mMANativeHelper == null) { 915 throw new IllegalStateException("The video editor is not initialized"); 916 } 917 918 if (mMediaItems.size() > 0) { 919 final Rect frame = surfaceHolder.getSurfaceFrame(); 920 result = mMANativeHelper.renderPreviewFrame(surface, 921 timeMs, frame.width(), frame.height(), overlayData); 922 } else { 923 result = 0; 924 } 925 } catch (InterruptedException ex) { 926 Log.w(TAG, "The thread was interrupted", new Throwable()); 927 throw new IllegalStateException("The thread was interrupted"); 928 } finally { 929 if (semAcquireDone) { 930 unlock(); 931 } 932 } 933 return result; 934 } 935 936 /** 937 * the project form XML 938 */ 939 private void load() throws FileNotFoundException, XmlPullParserException, IOException { 940 final File file = new File(mProjectPath, PROJECT_FILENAME); 941 /** 942 * Load the metadata 943 */ 944 final FileInputStream fis = new FileInputStream(file); 945 try { 946 final List<String> ignoredMediaItems = new ArrayList<String>(); 947 948 final XmlPullParser parser = Xml.newPullParser(); 949 parser.setInput(fis, "UTF-8"); 950 int eventType = parser.getEventType(); 951 String name; 952 MediaItem currentMediaItem = null; 953 Overlay currentOverlay = null; 954 boolean regenerateProjectThumbnail = false; 955 while (eventType != XmlPullParser.END_DOCUMENT) { 956 switch (eventType) { 957 case XmlPullParser.START_TAG: { 958 name = parser.getName(); 959 if (TAG_PROJECT.equals(name)) { 960 mAspectRatio = Integer.parseInt(parser.getAttributeValue("", 961 ATTR_ASPECT_RATIO)); 962 963 final boolean mRegenPCM = 964 Boolean.parseBoolean(parser.getAttributeValue("", 965 ATTR_REGENERATE_PCM)); 966 mMANativeHelper.setAudioflag(mRegenPCM); 967 } else if (TAG_MEDIA_ITEM.equals(name)) { 968 final String mediaItemId = parser.getAttributeValue("", ATTR_ID); 969 try { 970 currentMediaItem = parseMediaItem(parser); 971 mMediaItems.add(currentMediaItem); 972 } catch (Exception ex) { 973 Log.w(TAG, "Cannot load media item: " + mediaItemId, ex); 974 currentMediaItem = null; 975 976 // First media item is invalid, mark for project thumbnail removal 977 if (mMediaItems.size() == 0) { 978 regenerateProjectThumbnail = true; 979 } 980 // Ignore the media item 981 ignoredMediaItems.add(mediaItemId); 982 } 983 } else if (TAG_TRANSITION.equals(name)) { 984 try { 985 final Transition transition = parseTransition(parser, 986 ignoredMediaItems); 987 // The transition will be null if the bounding 988 // media items are ignored 989 if (transition != null) { 990 mTransitions.add(transition); 991 } 992 } catch (Exception ex) { 993 Log.w(TAG, "Cannot load transition", ex); 994 } 995 } else if (TAG_OVERLAY.equals(name)) { 996 if (currentMediaItem != null) { 997 try { 998 currentOverlay = parseOverlay(parser, currentMediaItem); 999 currentMediaItem.addOverlay(currentOverlay); 1000 } catch (Exception ex) { 1001 Log.w(TAG, "Cannot load overlay", ex); 1002 } 1003 } 1004 } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) { 1005 if (currentOverlay != null) { 1006 final int attributesCount = parser.getAttributeCount(); 1007 for (int i = 0; i < attributesCount; i++) { 1008 currentOverlay.setUserAttribute(parser.getAttributeName(i), 1009 parser.getAttributeValue(i)); 1010 } 1011 } 1012 } else if (TAG_EFFECT.equals(name)) { 1013 if (currentMediaItem != null) { 1014 try { 1015 final Effect effect = parseEffect(parser, currentMediaItem); 1016 currentMediaItem.addEffect(effect); 1017 1018 if (effect instanceof EffectKenBurns) { 1019 final boolean isImageClipGenerated = 1020 Boolean.parseBoolean(parser.getAttributeValue("", 1021 ATTR_IS_IMAGE_CLIP_GENERATED)); 1022 if(isImageClipGenerated) { 1023 final String filename = parser.getAttributeValue("", 1024 ATTR_GENERATED_IMAGE_CLIP); 1025 if (new File(filename).exists() == true) { 1026 ((MediaImageItem)currentMediaItem). 1027 setGeneratedImageClip(filename); 1028 ((MediaImageItem)currentMediaItem). 1029 setRegenerateClip(false); 1030 } else { 1031 ((MediaImageItem)currentMediaItem). 1032 setGeneratedImageClip(null); 1033 ((MediaImageItem)currentMediaItem). 1034 setRegenerateClip(true); 1035 } 1036 } else { 1037 ((MediaImageItem)currentMediaItem). 1038 setGeneratedImageClip(null); 1039 ((MediaImageItem)currentMediaItem). 1040 setRegenerateClip(true); 1041 } 1042 } 1043 } catch (Exception ex) { 1044 Log.w(TAG, "Cannot load effect", ex); 1045 } 1046 } 1047 } else if (TAG_AUDIO_TRACK.equals(name)) { 1048 try { 1049 final AudioTrack audioTrack = parseAudioTrack(parser); 1050 addAudioTrack(audioTrack); 1051 } catch (Exception ex) { 1052 Log.w(TAG, "Cannot load audio track", ex); 1053 } 1054 } 1055 break; 1056 } 1057 1058 case XmlPullParser.END_TAG: { 1059 name = parser.getName(); 1060 if (TAG_MEDIA_ITEM.equals(name)) { 1061 currentMediaItem = null; 1062 } else if (TAG_OVERLAY.equals(name)) { 1063 currentOverlay = null; 1064 } 1065 break; 1066 } 1067 1068 default: { 1069 break; 1070 } 1071 } 1072 eventType = parser.next(); 1073 } 1074 computeTimelineDuration(); 1075 // Regenerate project thumbnail 1076 if (regenerateProjectThumbnail) { 1077 generateProjectThumbnail(); 1078 regenerateProjectThumbnail = false; 1079 } 1080 } finally { 1081 if (fis != null) { 1082 fis.close(); 1083 } 1084 } 1085 } 1086 1087 /** 1088 * Parse the media item 1089 * 1090 * @param parser The parser 1091 * @return The media item 1092 */ 1093 private MediaItem parseMediaItem(XmlPullParser parser) throws IOException { 1094 final String mediaItemId = parser.getAttributeValue("", ATTR_ID); 1095 final String type = parser.getAttributeValue("", ATTR_TYPE); 1096 final String filename = parser.getAttributeValue("", ATTR_FILENAME); 1097 final int renderingMode = Integer.parseInt(parser.getAttributeValue("", 1098 ATTR_RENDERING_MODE)); 1099 1100 final MediaItem currentMediaItem; 1101 if (MediaImageItem.class.getSimpleName().equals(type)) { 1102 final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); 1103 currentMediaItem = new MediaImageItem(this, mediaItemId, filename, 1104 durationMs, renderingMode); 1105 } else if (MediaVideoItem.class.getSimpleName().equals(type)) { 1106 final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); 1107 final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME)); 1108 final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME)); 1109 final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED)); 1110 final String audioWaveformFilename = parser.getAttributeValue("", 1111 ATTR_AUDIO_WAVEFORM_FILENAME); 1112 currentMediaItem = new MediaVideoItem(this, mediaItemId, filename, 1113 renderingMode, beginMs, endMs, volume, muted, audioWaveformFilename); 1114 1115 final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); 1116 final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME)); 1117 ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs, endTimeMs); 1118 1119 final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME)); 1120 ((MediaVideoItem)currentMediaItem).setVolume(volumePercent); 1121 } else { 1122 throw new IllegalArgumentException("Unknown media item type: " + type); 1123 } 1124 1125 return currentMediaItem; 1126 } 1127 1128 /** 1129 * Parse the transition 1130 * 1131 * @param parser The parser 1132 * @param ignoredMediaItems The list of ignored media items 1133 * 1134 * @return The transition 1135 */ 1136 private Transition parseTransition(XmlPullParser parser, List<String> ignoredMediaItems) { 1137 final String transitionId = parser.getAttributeValue("", ATTR_ID); 1138 final String type = parser.getAttributeValue("", ATTR_TYPE); 1139 final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); 1140 final int behavior = Integer.parseInt(parser.getAttributeValue("", ATTR_BEHAVIOR)); 1141 1142 final String beforeMediaItemId = parser.getAttributeValue("", ATTR_BEFORE_MEDIA_ITEM_ID); 1143 final MediaItem beforeMediaItem; 1144 if (beforeMediaItemId != null) { 1145 if (ignoredMediaItems.contains(beforeMediaItemId)) { 1146 // This transition is ignored 1147 return null; 1148 } 1149 1150 beforeMediaItem = getMediaItem(beforeMediaItemId); 1151 } else { 1152 beforeMediaItem = null; 1153 } 1154 1155 final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID); 1156 final MediaItem afterMediaItem; 1157 if (afterMediaItemId != null) { 1158 if (ignoredMediaItems.contains(afterMediaItemId)) { 1159 // This transition is ignored 1160 return null; 1161 } 1162 1163 afterMediaItem = getMediaItem(afterMediaItemId); 1164 } else { 1165 afterMediaItem = null; 1166 } 1167 1168 final Transition transition; 1169 if (TransitionAlpha.class.getSimpleName().equals(type)) { 1170 final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING)); 1171 final String maskFilename = parser.getAttributeValue("", ATTR_MASK); 1172 final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT)); 1173 transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem, 1174 durationMs, behavior, maskFilename, blending, invert); 1175 } else if (TransitionCrossfade.class.getSimpleName().equals(type)) { 1176 transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem, 1177 durationMs, behavior); 1178 } else if (TransitionSliding.class.getSimpleName().equals(type)) { 1179 final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION)); 1180 transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem, 1181 durationMs, behavior, direction); 1182 } else if (TransitionFadeBlack.class.getSimpleName().equals(type)) { 1183 transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem, 1184 durationMs, behavior); 1185 } else { 1186 throw new IllegalArgumentException("Invalid transition type: " + type); 1187 } 1188 1189 final boolean isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("", 1190 ATTR_IS_TRANSITION_GENERATED)); 1191 if (isTransitionGenerated == true) { 1192 final String transitionFile = parser.getAttributeValue("", 1193 ATTR_GENERATED_TRANSITION_CLIP); 1194 1195 if (new File(transitionFile).exists()) { 1196 transition.setFilename(transitionFile); 1197 } else { 1198 transition.setFilename(null); 1199 } 1200 } 1201 1202 // Use the transition 1203 if (beforeMediaItem != null) { 1204 beforeMediaItem.setBeginTransition(transition); 1205 } 1206 1207 if (afterMediaItem != null) { 1208 afterMediaItem.setEndTransition(transition); 1209 } 1210 1211 return transition; 1212 } 1213 1214 /** 1215 * Parse the overlay 1216 * 1217 * @param parser The parser 1218 * @param mediaItem The media item owner 1219 * 1220 * @return The overlay 1221 */ 1222 private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) { 1223 final String overlayId = parser.getAttributeValue("", ATTR_ID); 1224 final String type = parser.getAttributeValue("", ATTR_TYPE); 1225 final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); 1226 final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); 1227 1228 final Overlay overlay; 1229 if (OverlayFrame.class.getSimpleName().equals(type)) { 1230 final String filename = parser.getAttributeValue("", ATTR_FILENAME); 1231 overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs); 1232 } else { 1233 throw new IllegalArgumentException("Invalid overlay type: " + type); 1234 } 1235 1236 final String overlayRgbFileName = parser.getAttributeValue("", ATTR_OVERLAY_RGB_FILENAME); 1237 if (overlayRgbFileName != null) { 1238 ((OverlayFrame)overlay).setFilename(overlayRgbFileName); 1239 1240 final int overlayFrameWidth = Integer.parseInt(parser.getAttributeValue("", 1241 ATTR_OVERLAY_FRAME_WIDTH)); 1242 final int overlayFrameHeight = Integer.parseInt(parser.getAttributeValue("", 1243 ATTR_OVERLAY_FRAME_HEIGHT)); 1244 1245 ((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth); 1246 ((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight); 1247 1248 final int resizedRGBFrameWidth = Integer.parseInt(parser.getAttributeValue("", 1249 ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH)); 1250 final int resizedRGBFrameHeight = Integer.parseInt(parser.getAttributeValue("", 1251 ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT)); 1252 1253 ((OverlayFrame)overlay).setResizedRGBSize(resizedRGBFrameWidth, resizedRGBFrameHeight); 1254 } 1255 1256 return overlay; 1257 } 1258 1259 /** 1260 * Parse the effect 1261 * 1262 * @param parser The parser 1263 * @param mediaItem The media item owner 1264 * 1265 * @return The effect 1266 */ 1267 private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) { 1268 final String effectId = parser.getAttributeValue("", ATTR_ID); 1269 final String type = parser.getAttributeValue("", ATTR_TYPE); 1270 final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); 1271 final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); 1272 1273 final Effect effect; 1274 if (EffectColor.class.getSimpleName().equals(type)) { 1275 final int colorEffectType = Integer.parseInt(parser.getAttributeValue("", 1276 ATTR_COLOR_EFFECT_TYPE)); 1277 final int color; 1278 if (colorEffectType == EffectColor.TYPE_COLOR 1279 || colorEffectType == EffectColor.TYPE_GRADIENT) { 1280 color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE)); 1281 } else { 1282 color = 0; 1283 } 1284 effect = new EffectColor(mediaItem, effectId, startTimeMs, 1285 durationMs, colorEffectType, color); 1286 } else if (EffectKenBurns.class.getSimpleName().equals(type)) { 1287 final Rect startRect = new Rect( 1288 Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_LEFT)), 1289 Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_TOP)), 1290 Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_RIGHT)), 1291 Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_BOTTOM))); 1292 final Rect endRect = new Rect( 1293 Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_LEFT)), 1294 Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_TOP)), 1295 Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_RIGHT)), 1296 Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_BOTTOM))); 1297 effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect, 1298 startTimeMs, durationMs); 1299 } else { 1300 throw new IllegalArgumentException("Invalid effect type: " + type); 1301 } 1302 1303 return effect; 1304 } 1305 1306 /** 1307 * Parse the audio track 1308 * 1309 * @param parser The parser 1310 * 1311 * @return The audio track 1312 */ 1313 private AudioTrack parseAudioTrack(XmlPullParser parser) throws IOException { 1314 final String audioTrackId = parser.getAttributeValue("", ATTR_ID); 1315 final String filename = parser.getAttributeValue("", ATTR_FILENAME); 1316 final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME)); 1317 final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); 1318 final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME)); 1319 final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME)); 1320 final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED)); 1321 final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP)); 1322 final boolean duckingEnabled = Boolean.parseBoolean( 1323 parser.getAttributeValue("", ATTR_DUCK_ENABLED)); 1324 final int duckThreshold = Integer.parseInt( 1325 parser.getAttributeValue("", ATTR_DUCK_THRESHOLD)); 1326 final int duckedTrackVolume = Integer.parseInt(parser.getAttributeValue("", 1327 ATTR_DUCKED_TRACK_VOLUME)); 1328 1329 final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME); 1330 final AudioTrack audioTrack = new AudioTrack(this, audioTrackId, 1331 filename, startTimeMs, 1332 beginMs, endMs, loop, 1333 volume, muted, 1334 duckingEnabled, 1335 duckThreshold, 1336 duckedTrackVolume, 1337 waveformFilename); 1338 1339 return audioTrack; 1340 } 1341 1342 /* 1343 * {@inheritDoc} 1344 */ 1345 public void save() throws IOException { 1346 final XmlSerializer serializer = Xml.newSerializer(); 1347 final StringWriter writer = new StringWriter(); 1348 serializer.setOutput(writer); 1349 serializer.startDocument("UTF-8", true); 1350 serializer.startTag("", TAG_PROJECT); 1351 serializer.attribute("", 1352 ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio)); 1353 1354 serializer.attribute("", ATTR_REGENERATE_PCM, 1355 Boolean.toString(mMANativeHelper.getAudioflag())); 1356 1357 serializer.startTag("", TAG_MEDIA_ITEMS); 1358 for (MediaItem mediaItem : mMediaItems) { 1359 serializer.startTag("", TAG_MEDIA_ITEM); 1360 serializer.attribute("", ATTR_ID, mediaItem.getId()); 1361 serializer.attribute("", ATTR_TYPE, 1362 mediaItem.getClass().getSimpleName()); 1363 serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename()); 1364 serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString( 1365 mediaItem.getRenderingMode())); 1366 if (mediaItem instanceof MediaVideoItem) { 1367 final MediaVideoItem mvi = (MediaVideoItem)mediaItem; 1368 serializer 1369 .attribute("", ATTR_BEGIN_TIME, 1370 Long.toString(mvi.getBoundaryBeginTime())); 1371 serializer.attribute("", ATTR_END_TIME, 1372 Long.toString(mvi.getBoundaryEndTime())); 1373 serializer.attribute("", ATTR_VOLUME, 1374 Integer.toString(mvi.getVolume())); 1375 serializer.attribute("", ATTR_MUTED, 1376 Boolean.toString(mvi.isMuted())); 1377 if (mvi.getAudioWaveformFilename() != null) { 1378 serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME, 1379 mvi.getAudioWaveformFilename()); 1380 } 1381 } else if (mediaItem instanceof MediaImageItem) { 1382 serializer.attribute("", ATTR_DURATION, 1383 Long.toString(mediaItem.getTimelineDuration())); 1384 } 1385 1386 final List<Overlay> overlays = mediaItem.getAllOverlays(); 1387 if (overlays.size() > 0) { 1388 serializer.startTag("", TAG_OVERLAYS); 1389 for (Overlay overlay : overlays) { 1390 serializer.startTag("", TAG_OVERLAY); 1391 serializer.attribute("", ATTR_ID, overlay.getId()); 1392 serializer.attribute("", 1393 ATTR_TYPE, overlay.getClass().getSimpleName()); 1394 serializer.attribute("", ATTR_BEGIN_TIME, 1395 Long.toString(overlay.getStartTime())); 1396 serializer.attribute("", ATTR_DURATION, 1397 Long.toString(overlay.getDuration())); 1398 if (overlay instanceof OverlayFrame) { 1399 final OverlayFrame overlayFrame = (OverlayFrame)overlay; 1400 overlayFrame.save(getPath()); 1401 if (overlayFrame.getBitmapImageFileName() != null) { 1402 serializer.attribute("", ATTR_FILENAME, 1403 overlayFrame.getBitmapImageFileName()); 1404 } 1405 1406 if (overlayFrame.getFilename() != null) { 1407 serializer.attribute("", 1408 ATTR_OVERLAY_RGB_FILENAME, 1409 overlayFrame.getFilename()); 1410 serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH, 1411 Integer.toString(overlayFrame.getOverlayFrameWidth())); 1412 serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT, 1413 Integer.toString(overlayFrame.getOverlayFrameHeight())); 1414 serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH, 1415 Integer.toString(overlayFrame.getResizedRGBSizeWidth())); 1416 serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT, 1417 Integer.toString(overlayFrame.getResizedRGBSizeHeight())); 1418 1419 } 1420 1421 } 1422 1423 /** 1424 * Save the user attributes 1425 */ 1426 serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES); 1427 final Map<String, String> userAttributes = overlay.getUserAttributes(); 1428 for (String name : userAttributes.keySet()) { 1429 final String value = userAttributes.get(name); 1430 if (value != null) { 1431 serializer.attribute("", name, value); 1432 } 1433 } 1434 serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES); 1435 1436 serializer.endTag("", TAG_OVERLAY); 1437 } 1438 serializer.endTag("", TAG_OVERLAYS); 1439 } 1440 1441 final List<Effect> effects = mediaItem.getAllEffects(); 1442 if (effects.size() > 0) { 1443 serializer.startTag("", TAG_EFFECTS); 1444 for (Effect effect : effects) { 1445 serializer.startTag("", TAG_EFFECT); 1446 serializer.attribute("", ATTR_ID, effect.getId()); 1447 serializer.attribute("", 1448 ATTR_TYPE, effect.getClass().getSimpleName()); 1449 serializer.attribute("", ATTR_BEGIN_TIME, 1450 Long.toString(effect.getStartTime())); 1451 serializer.attribute("", ATTR_DURATION, 1452 Long.toString(effect.getDuration())); 1453 if (effect instanceof EffectColor) { 1454 final EffectColor colorEffect = (EffectColor)effect; 1455 serializer.attribute("", ATTR_COLOR_EFFECT_TYPE, 1456 Integer.toString(colorEffect.getType())); 1457 if (colorEffect.getType() == EffectColor.TYPE_COLOR || 1458 colorEffect.getType() == EffectColor.TYPE_GRADIENT) { 1459 serializer.attribute("", ATTR_COLOR_EFFECT_VALUE, 1460 Integer.toString(colorEffect.getColor())); 1461 } 1462 } else if (effect instanceof EffectKenBurns) { 1463 final Rect startRect = ((EffectKenBurns)effect).getStartRect(); 1464 serializer.attribute("", ATTR_START_RECT_LEFT, 1465 Integer.toString(startRect.left)); 1466 serializer.attribute("", ATTR_START_RECT_TOP, 1467 Integer.toString(startRect.top)); 1468 serializer.attribute("", ATTR_START_RECT_RIGHT, 1469 Integer.toString(startRect.right)); 1470 serializer.attribute("", ATTR_START_RECT_BOTTOM, 1471 Integer.toString(startRect.bottom)); 1472 1473 final Rect endRect = ((EffectKenBurns)effect).getEndRect(); 1474 serializer.attribute("", ATTR_END_RECT_LEFT, 1475 Integer.toString(endRect.left)); 1476 serializer.attribute("", ATTR_END_RECT_TOP, 1477 Integer.toString(endRect.top)); 1478 serializer.attribute("", ATTR_END_RECT_RIGHT, 1479 Integer.toString(endRect.right)); 1480 serializer.attribute("", ATTR_END_RECT_BOTTOM, 1481 Integer.toString(endRect.bottom)); 1482 final MediaItem mItem = effect.getMediaItem(); 1483 if(((MediaImageItem)mItem).getGeneratedImageClip() != null) { 1484 serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED, 1485 Boolean.toString(true)); 1486 serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP, 1487 ((MediaImageItem)mItem).getGeneratedImageClip()); 1488 } else { 1489 serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED, 1490 Boolean.toString(false)); 1491 } 1492 } 1493 1494 serializer.endTag("", TAG_EFFECT); 1495 } 1496 serializer.endTag("", TAG_EFFECTS); 1497 } 1498 1499 serializer.endTag("", TAG_MEDIA_ITEM); 1500 } 1501 serializer.endTag("", TAG_MEDIA_ITEMS); 1502 1503 serializer.startTag("", TAG_TRANSITIONS); 1504 1505 for (Transition transition : mTransitions) { 1506 serializer.startTag("", TAG_TRANSITION); 1507 serializer.attribute("", ATTR_ID, transition.getId()); 1508 serializer.attribute("", ATTR_TYPE, transition.getClass().getSimpleName()); 1509 serializer.attribute("", ATTR_DURATION, Long.toString(transition.getDuration())); 1510 serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(transition.getBehavior())); 1511 serializer.attribute("", ATTR_IS_TRANSITION_GENERATED, 1512 Boolean.toString(transition.isGenerated())); 1513 if (transition.isGenerated() == true) { 1514 serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP, transition.mFilename); 1515 } 1516 final MediaItem afterMediaItem = transition.getAfterMediaItem(); 1517 if (afterMediaItem != null) { 1518 serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID, afterMediaItem.getId()); 1519 } 1520 1521 final MediaItem beforeMediaItem = transition.getBeforeMediaItem(); 1522 if (beforeMediaItem != null) { 1523 serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID, beforeMediaItem.getId()); 1524 } 1525 1526 if (transition instanceof TransitionSliding) { 1527 serializer.attribute("", ATTR_DIRECTION, 1528 Integer.toString(((TransitionSliding)transition).getDirection())); 1529 } else if (transition instanceof TransitionAlpha) { 1530 TransitionAlpha ta = (TransitionAlpha)transition; 1531 serializer.attribute("", ATTR_BLENDING, 1532 Integer.toString(ta.getBlendingPercent())); 1533 serializer.attribute("", ATTR_INVERT, 1534 Boolean.toString(ta.isInvert())); 1535 if (ta.getMaskFilename() != null) { 1536 serializer.attribute("", ATTR_MASK, ta.getMaskFilename()); 1537 } 1538 } 1539 serializer.endTag("", TAG_TRANSITION); 1540 } 1541 serializer.endTag("", TAG_TRANSITIONS); 1542 serializer.startTag("", TAG_AUDIO_TRACKS); 1543 for (AudioTrack at : mAudioTracks) { 1544 serializer.startTag("", TAG_AUDIO_TRACK); 1545 serializer.attribute("", ATTR_ID, at.getId()); 1546 serializer.attribute("", ATTR_FILENAME, at.getFilename()); 1547 serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime())); 1548 serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime())); 1549 serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime())); 1550 serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume())); 1551 serializer.attribute("", ATTR_DUCK_ENABLED, 1552 Boolean.toString(at.isDuckingEnabled())); 1553 serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME, 1554 Integer.toString(at.getDuckedTrackVolume())); 1555 serializer.attribute("", ATTR_DUCK_THRESHOLD, 1556 Integer.toString(at.getDuckingThreshhold())); 1557 serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted())); 1558 serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping())); 1559 if (at.getAudioWaveformFilename() != null) { 1560 serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME, 1561 at.getAudioWaveformFilename()); 1562 } 1563 1564 serializer.endTag("", TAG_AUDIO_TRACK); 1565 } 1566 serializer.endTag("", TAG_AUDIO_TRACKS); 1567 1568 serializer.endTag("", TAG_PROJECT); 1569 serializer.endDocument(); 1570 1571 /** 1572 * Save the metadata XML file 1573 */ 1574 final FileOutputStream out = new FileOutputStream(new File(getPath(), 1575 PROJECT_FILENAME)); 1576 out.write(writer.toString().getBytes()); 1577 out.flush(); 1578 out.close(); 1579 } 1580 1581 /* 1582 * {@inheritDoc} 1583 */ 1584 public void setAspectRatio(int aspectRatio) { 1585 mAspectRatio = aspectRatio; 1586 /** 1587 * Invalidate all transitions 1588 */ 1589 mMANativeHelper.setGeneratePreview(true); 1590 1591 for (Transition transition : mTransitions) { 1592 transition.invalidate(); 1593 } 1594 1595 final Iterator<MediaItem> it = mMediaItems.iterator(); 1596 1597 while (it.hasNext()) { 1598 final MediaItem t = it.next(); 1599 List<Overlay> overlayList = t.getAllOverlays(); 1600 for (Overlay overlay : overlayList) { 1601 1602 ((OverlayFrame)overlay).invalidateGeneratedFiles(); 1603 } 1604 } 1605 } 1606 1607 /* 1608 * {@inheritDoc} 1609 */ 1610 public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, 1611 boolean loop, int callbackAfterFrameCount, 1612 PreviewProgressListener listener) { 1613 1614 if (surfaceHolder == null) { 1615 throw new IllegalArgumentException(); 1616 } 1617 1618 final Surface surface = surfaceHolder.getSurface(); 1619 if (surface == null) { 1620 throw new IllegalArgumentException("Surface could not be retrieved from surface holder"); 1621 } 1622 1623 if (listener == null) { 1624 throw new IllegalArgumentException(); 1625 } 1626 1627 if (fromMs >= mDurationMs) { 1628 throw new IllegalArgumentException("Requested time not correct"); 1629 } 1630 1631 if (fromMs < 0) { 1632 throw new IllegalArgumentException("Requested time not correct"); 1633 } 1634 1635 boolean semAcquireDone = false; 1636 if (!mPreviewInProgress) { 1637 try{ 1638 semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS); 1639 if (semAcquireDone == false) { 1640 throw new IllegalStateException("Timeout waiting for semaphore"); 1641 } 1642 1643 if (mMANativeHelper == null) { 1644 throw new IllegalStateException("The video editor is not initialized"); 1645 } 1646 1647 if (mMediaItems.size() > 0) { 1648 mPreviewInProgress = true; 1649 mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, 1650 mAudioTracks, null); 1651 mMANativeHelper.doPreview(surface, fromMs, toMs, loop, 1652 callbackAfterFrameCount, listener); 1653 } 1654 /** 1655 * Release The lock on complete by calling stopPreview 1656 */ 1657 } catch (InterruptedException ex) { 1658 Log.w(TAG, "The thread was interrupted", new Throwable()); 1659 throw new IllegalStateException("The thread was interrupted"); 1660 } 1661 } else { 1662 throw new IllegalStateException("Preview already in progress"); 1663 } 1664 } 1665 1666 /* 1667 * {@inheritDoc} 1668 */ 1669 public long stopPreview() { 1670 long result = 0; 1671 if (mPreviewInProgress) { 1672 try { 1673 result = mMANativeHelper.stopPreview(); 1674 /** 1675 * release on complete by calling stopPreview 1676 */ 1677 } finally { 1678 mPreviewInProgress = false; 1679 unlock(); 1680 } 1681 return result; 1682 } 1683 else { 1684 return 0; 1685 } 1686 } 1687 1688 /* 1689 * Remove transitions associated with the specified media item 1690 * 1691 * @param mediaItem The media item 1692 */ 1693 private void removeAdjacentTransitions(MediaItem mediaItem) { 1694 final Transition beginTransition = mediaItem.getBeginTransition(); 1695 if (beginTransition != null) { 1696 if (beginTransition.getAfterMediaItem() != null) { 1697 beginTransition.getAfterMediaItem().setEndTransition(null); 1698 } 1699 beginTransition.invalidate(); 1700 mTransitions.remove(beginTransition); 1701 } 1702 1703 final Transition endTransition = mediaItem.getEndTransition(); 1704 if (endTransition != null) { 1705 if (endTransition.getBeforeMediaItem() != null) { 1706 endTransition.getBeforeMediaItem().setBeginTransition(null); 1707 } 1708 endTransition.invalidate(); 1709 mTransitions.remove(endTransition); 1710 } 1711 1712 mediaItem.setBeginTransition(null); 1713 mediaItem.setEndTransition(null); 1714 } 1715 1716 /** 1717 * Remove the transition before this media item 1718 * 1719 * @param index The media item index 1720 */ 1721 private void removeTransitionBefore(int index) { 1722 final MediaItem mediaItem = mMediaItems.get(index); 1723 final Iterator<Transition> it = mTransitions.iterator(); 1724 while (it.hasNext()) { 1725 Transition t = it.next(); 1726 if (t.getBeforeMediaItem() == mediaItem) { 1727 mMANativeHelper.setGeneratePreview(true); 1728 it.remove(); 1729 t.invalidate(); 1730 mediaItem.setBeginTransition(null); 1731 if (index > 0) { 1732 mMediaItems.get(index - 1).setEndTransition(null); 1733 } 1734 break; 1735 } 1736 } 1737 } 1738 1739 /** 1740 * Remove the transition after this media item 1741 * 1742 * @param mediaItem The media item 1743 */ 1744 private void removeTransitionAfter(int index) { 1745 final MediaItem mediaItem = mMediaItems.get(index); 1746 final Iterator<Transition> it = mTransitions.iterator(); 1747 while (it.hasNext()) { 1748 Transition t = it.next(); 1749 if (t.getAfterMediaItem() == mediaItem) { 1750 mMANativeHelper.setGeneratePreview(true); 1751 it.remove(); 1752 t.invalidate(); 1753 mediaItem.setEndTransition(null); 1754 /** 1755 * Invalidate the reference in the next media item 1756 */ 1757 if (index < mMediaItems.size() - 1) { 1758 mMediaItems.get(index + 1).setBeginTransition(null); 1759 } 1760 break; 1761 } 1762 } 1763 } 1764 1765 /** 1766 * Compute the duration 1767 */ 1768 private void computeTimelineDuration() { 1769 mDurationMs = 0; 1770 final int mediaItemsCount = mMediaItems.size(); 1771 for (int i = 0; i < mediaItemsCount; i++) { 1772 final MediaItem mediaItem = mMediaItems.get(i); 1773 mDurationMs += mediaItem.getTimelineDuration(); 1774 if (mediaItem.getEndTransition() != null) { 1775 if (i < mediaItemsCount - 1) { 1776 mDurationMs -= mediaItem.getEndTransition().getDuration(); 1777 } 1778 } 1779 } 1780 } 1781 1782 /* 1783 * Generate the project thumbnail 1784 */ 1785 private void generateProjectThumbnail() { 1786 /* 1787 * If a thumbnail already exists, then delete it first 1788 */ 1789 if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) { 1790 (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete(); 1791 } 1792 /* 1793 * Generate a new thumbnail for the project from first media Item 1794 */ 1795 if (mMediaItems.size() > 0) { 1796 MediaItem mI = mMediaItems.get(0); 1797 /* 1798 * Lets initialize the width for default aspect ratio i.e 16:9 1799 */ 1800 int height = 480; 1801 int width = 854; 1802 switch (mI.getAspectRatio()) { 1803 case MediaProperties.ASPECT_RATIO_3_2: 1804 width = 720; 1805 break; 1806 case MediaProperties.ASPECT_RATIO_4_3: 1807 width = 640; 1808 break; 1809 case MediaProperties.ASPECT_RATIO_5_3: 1810 width = 800; 1811 break; 1812 case MediaProperties.ASPECT_RATIO_11_9: 1813 width = 586; 1814 break; 1815 case MediaProperties.ASPECT_RATIO_16_9: 1816 case MediaProperties.ASPECT_RATIO_UNDEFINED: 1817 break; 1818 } 1819 1820 Bitmap projectBitmap = null; 1821 try { 1822 projectBitmap = mI.getThumbnail(width, height, 500); 1823 } catch (IllegalArgumentException e) { 1824 throw new IllegalArgumentException ("Illegal argument error creating project thumbnail"); 1825 } catch (IOException e) { 1826 throw new IllegalArgumentException ("IO Error creating project thumbnail"); 1827 } 1828 1829 try { 1830 FileOutputStream stream = new FileOutputStream(mProjectPath + "/" 1831 + THUMBNAIL_FILENAME); 1832 projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); 1833 stream.flush(); 1834 stream.close(); 1835 } catch (IOException e) { 1836 throw new IllegalArgumentException ("Error creating project thumbnail"); 1837 } finally { 1838 projectBitmap.recycle(); 1839 } 1840 } 1841 } 1842 1843 /** 1844 * Clears the preview surface 1845 * 1846 * @param surfaceHolder SurfaceHolder where the preview is rendered 1847 * and needs to be cleared. 1848 */ 1849 public void clearSurface(SurfaceHolder surfaceHolder) { 1850 if (surfaceHolder == null) { 1851 throw new IllegalArgumentException("Invalid surface holder"); 1852 } 1853 1854 final Surface surface = surfaceHolder.getSurface(); 1855 if (surface == null) { 1856 throw new IllegalArgumentException("Surface could not be retrieved from surface holder"); 1857 } 1858 1859 if (mMANativeHelper != null) { 1860 mMANativeHelper.clearPreviewSurface(surface); 1861 } else { 1862 Log.w(TAG, "Native helper was not ready!"); 1863 } 1864 } 1865 1866 /** 1867 * Grab the semaphore which arbitrates access to the editor 1868 * 1869 * @throws InterruptedException 1870 */ 1871 private void lock() throws InterruptedException { 1872 if (Log.isLoggable(TAG, Log.DEBUG)) { 1873 Log.d(TAG, "lock: grabbing semaphore", new Throwable()); 1874 } 1875 mLock.acquire(); 1876 if (Log.isLoggable(TAG, Log.DEBUG)) { 1877 Log.d(TAG, "lock: grabbed semaphore"); 1878 } 1879 } 1880 1881 /** 1882 * Tries to grab the semaphore with a specified time out which arbitrates access to the editor 1883 * 1884 * @param timeoutMs time out in ms. 1885 * 1886 * @return true if the semaphore is acquired, false otherwise 1887 * @throws InterruptedException 1888 */ 1889 private boolean lock(long timeoutMs) throws InterruptedException { 1890 if (Log.isLoggable(TAG, Log.DEBUG)) { 1891 Log.d(TAG, "lock: grabbing semaphore with timeout " + timeoutMs, new Throwable()); 1892 } 1893 1894 boolean acquireSem = mLock.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); 1895 if (Log.isLoggable(TAG, Log.DEBUG)) { 1896 Log.d(TAG, "lock: grabbed semaphore status " + acquireSem); 1897 } 1898 1899 return acquireSem; 1900 } 1901 1902 /** 1903 * Release the semaphore which arbitrates access to the editor 1904 */ 1905 private void unlock() { 1906 if (Log.isLoggable(TAG, Log.DEBUG)) { 1907 Log.d(TAG, "unlock: releasing semaphore"); 1908 } 1909 mLock.release(); 1910 } 1911 1912 /** 1913 * Dumps the heap memory usage information to file 1914 */ 1915 private static void dumpHeap (String filename) throws Exception { 1916 /* Cleanup as much as possible before dump 1917 */ 1918 System.gc(); 1919 System.runFinalization(); 1920 Thread.sleep(1000); 1921 String state = Environment.getExternalStorageState(); 1922 if (Environment.MEDIA_MOUNTED.equals(state)) { 1923 String extDir = 1924 Environment.getExternalStorageDirectory().toString(); 1925 1926 /* If dump file already exists, then delete it first 1927 */ 1928 if ((new File(extDir + "/" + filename + ".dump")).exists()) { 1929 (new File(extDir + "/" + filename + ".dump")).delete(); 1930 } 1931 /* Dump native heap 1932 */ 1933 FileOutputStream ost = 1934 new FileOutputStream(extDir + "/" + filename + ".dump"); 1935 Debug.dumpNativeHeap(ost.getFD()); 1936 ost.close(); 1937 } 1938 } 1939} 1940