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 */ VideoEditorImpl(String projectPath)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 */ getNativeContext()181 MediaArtistNativeHelper getNativeContext() { 182 return mMANativeHelper; 183 } 184 185 /* 186 * {@inheritDoc} 187 */ addAudioTrack(AudioTrack audioTrack)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 */ addMediaItem(MediaItem mediaItem)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 */ addTransition(Transition transition)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 */ cancelExport(String filename)329 public void cancelExport(String filename) { 330 if (mMANativeHelper != null && filename != null) { 331 mMANativeHelper.stop(filename); 332 } 333 } 334 335 /* 336 * {@inheritDoc} 337 */ export(String filename, int height, int bitrate, int audioCodec, int videoCodec, ExportProgressListener listener)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 */ export(String filename, int height, int bitrate, ExportProgressListener listener)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 */ generatePreview(MediaProcessingProgressListener listener)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 */ getAllAudioTracks()510 public List<AudioTrack> getAllAudioTracks() { 511 return mAudioTracks; 512 } 513 514 /* 515 * {@inheritDoc} 516 */ getAllMediaItems()517 public List<MediaItem> getAllMediaItems() { 518 return mMediaItems; 519 } 520 521 /* 522 * {@inheritDoc} 523 */ getAllTransitions()524 public List<Transition> getAllTransitions() { 525 return mTransitions; 526 } 527 528 /* 529 * {@inheritDoc} 530 */ getAspectRatio()531 public int getAspectRatio() { 532 return mAspectRatio; 533 } 534 535 /* 536 * {@inheritDoc} 537 */ getAudioTrack(String audioTrackId)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 */ getDuration()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 */ updateTimelineDuration()562 void updateTimelineDuration() { 563 computeTimelineDuration(); 564 } 565 566 /* 567 * {@inheritDoc} 568 */ getMediaItem(String mediaItemId)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 */ getPath()581 public String getPath() { 582 return mProjectPath; 583 } 584 585 /* 586 * {@inheritDoc} 587 */ getTransition(String transitionId)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 */ insertAudioTrack(AudioTrack audioTrack, String afterAudioTrackId)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 */ insertMediaItem(MediaItem mediaItem, String afterMediaItemId)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 */ moveAudioTrack(String audioTrackId, String afterAudioTrackId)670 public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) { 671 throw new IllegalStateException("Not supported"); 672 } 673 674 /* 675 * {@inheritDoc} 676 */ moveMediaItem(String mediaItemId, String afterMediaItemId)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 */ release()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 */ removeAllMediaItems()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 */ removeAudioTrack(String audioTrackId)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 */ removeMediaItem(String mediaItemId)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 removeMediaItem(String mediaItemId, boolean flag)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 */ removeTransition(String transitionId)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 */ renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs, OverlayData overlayData)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 */ load()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 */ parseMediaItem(XmlPullParser parser)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 */ parseTransition(XmlPullParser parser, List<String> ignoredMediaItems)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 */ parseOverlay(XmlPullParser parser, MediaItem mediaItem)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 */ parseEffect(XmlPullParser parser, MediaItem mediaItem)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 */ parseAudioTrack(XmlPullParser parser)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 */ save()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 */ setAspectRatio(int aspectRatio)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 */ startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop, int callbackAfterFrameCount, PreviewProgressListener listener)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 */ stopPreview()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 */ removeAdjacentTransitions(MediaItem mediaItem)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 */ removeTransitionBefore(int index)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 */ removeTransitionAfter(int index)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 */ computeTimelineDuration()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 */ generateProjectThumbnail()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 */ clearSurface(SurfaceHolder surfaceHolder)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 */ lock()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 */ lock(long timeoutMs)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 */ unlock()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 */ dumpHeap(String filename)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