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