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