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