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