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