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