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