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.FileNotFoundException;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.util.ArrayList;
25import java.util.List;
26
27import java.io.DataOutputStream;
28import java.nio.ByteBuffer;
29import java.nio.IntBuffer;
30
31import android.graphics.Bitmap;
32import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings;
33import android.media.videoeditor.MediaArtistNativeHelper.FileType;
34import android.media.videoeditor.MediaArtistNativeHelper.MediaRendering;
35
36/**
37 * This abstract class describes the base class for any MediaItem. Objects are
38 * defined with a file path as a source data.
39 * {@hide}
40 */
41public abstract class MediaItem {
42    /**
43     *  A constant which can be used to specify the end of the file (instead of
44     *  providing the actual duration of the media item).
45     */
46    public final static int END_OF_FILE = -1;
47
48    /**
49     *  Rendering modes
50     */
51    /**
52     * When using the RENDERING_MODE_BLACK_BORDER rendering mode video frames
53     * are resized by preserving the aspect ratio until the movie matches one of
54     * the dimensions of the output movie. The areas outside the resized video
55     * clip are rendered black.
56     */
57    public static final int RENDERING_MODE_BLACK_BORDER = 0;
58
59    /**
60     * When using the RENDERING_MODE_STRETCH rendering mode video frames are
61     * stretched horizontally or vertically to match the current aspect ratio of
62     * the video editor.
63     */
64    public static final int RENDERING_MODE_STRETCH = 1;
65
66    /**
67     * When using the RENDERING_MODE_CROPPING rendering mode video frames are
68     * scaled horizontally or vertically by preserving the original aspect ratio
69     * of the media item.
70     */
71    public static final int RENDERING_MODE_CROPPING = 2;
72
73    /**
74     *  The unique id of the MediaItem
75     */
76    private final String mUniqueId;
77
78    /**
79     *  The name of the file associated with the MediaItem
80     */
81    protected final String mFilename;
82
83    /**
84     *  List of effects
85     */
86    private final List<Effect> mEffects;
87
88    /**
89     *  List of overlays
90     */
91    private final List<Overlay> mOverlays;
92
93    /**
94     *  The rendering mode
95     */
96    private int mRenderingMode;
97
98    private final MediaArtistNativeHelper mMANativeHelper;
99
100    private final String mProjectPath;
101
102    /**
103     *  Beginning and end transitions
104     */
105    protected Transition mBeginTransition;
106
107    protected Transition mEndTransition;
108
109    protected String mGeneratedImageClip;
110
111    protected boolean mRegenerateClip;
112
113    private boolean mBlankFrameGenerated = false;
114
115    private String mBlankFrameFilename = null;
116
117    /**
118     * Constructor
119     *
120     * @param editor The video editor reference
121     * @param mediaItemId The MediaItem id
122     * @param filename name of the media file.
123     * @param renderingMode The rendering mode
124     * @throws IOException if file is not found
125     * @throws IllegalArgumentException if a capability such as file format is
126     *             not supported the exception object contains the unsupported
127     *             capability
128     */
129    protected MediaItem(VideoEditor editor, String mediaItemId, String filename,
130                        int renderingMode) throws IOException {
131        if (filename == null) {
132            throw new IllegalArgumentException("MediaItem : filename is null");
133        }
134        File file = new File(filename);
135        if (!file.exists()) {
136            throw new IOException(filename + " not found ! ");
137        }
138
139        /*Compare file_size with 2GB*/
140        if (VideoEditor.MAX_SUPPORTED_FILE_SIZE <= file.length()) {
141            throw new IllegalArgumentException("File size is more than 2GB");
142        }
143        mUniqueId = mediaItemId;
144        mFilename = filename;
145        mRenderingMode = renderingMode;
146        mEffects = new ArrayList<Effect>();
147        mOverlays = new ArrayList<Overlay>();
148        mBeginTransition = null;
149        mEndTransition = null;
150        mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext();
151        mProjectPath = editor.getPath();
152        mRegenerateClip = false;
153        mGeneratedImageClip = null;
154    }
155
156    /**
157     * @return The id of the media item
158     */
159    public String getId() {
160        return mUniqueId;
161    }
162
163    /**
164     * @return The media source file name
165     */
166    public String getFilename() {
167        return mFilename;
168    }
169
170    /**
171     * If aspect ratio of the MediaItem is different from the aspect ratio of
172     * the editor then this API controls the rendering mode.
173     *
174     * @param renderingMode rendering mode. It is one of:
175     *            {@link #RENDERING_MODE_BLACK_BORDER},
176     *            {@link #RENDERING_MODE_STRETCH}
177     */
178    public void setRenderingMode(int renderingMode) {
179        switch (renderingMode) {
180            case RENDERING_MODE_BLACK_BORDER:
181            case RENDERING_MODE_STRETCH:
182            case RENDERING_MODE_CROPPING:
183                break;
184
185            default:
186                throw new IllegalArgumentException("Invalid Rendering Mode");
187        }
188
189        mMANativeHelper.setGeneratePreview(true);
190
191        mRenderingMode = renderingMode;
192        if (mBeginTransition != null) {
193            mBeginTransition.invalidate();
194        }
195
196        if (mEndTransition != null) {
197            mEndTransition.invalidate();
198        }
199
200        for (Overlay overlay : mOverlays) {
201            ((OverlayFrame)overlay).invalidateGeneratedFiles();
202        }
203    }
204
205    /**
206     * @return The rendering mode
207     */
208    public int getRenderingMode() {
209        return mRenderingMode;
210    }
211
212    /**
213     * @param transition The beginning transition
214     */
215    void setBeginTransition(Transition transition) {
216        mBeginTransition = transition;
217    }
218
219    /**
220     * @return The begin transition
221     */
222    public Transition getBeginTransition() {
223        return mBeginTransition;
224    }
225
226    /**
227     * @param transition The end transition
228     */
229    void setEndTransition(Transition transition) {
230        mEndTransition = transition;
231    }
232
233    /**
234     * @return The end transition
235     */
236    public Transition getEndTransition() {
237        return mEndTransition;
238    }
239
240    /**
241     * @return The timeline duration. This is the actual duration in the
242     *         timeline (trimmed duration)
243     */
244    public abstract long getTimelineDuration();
245
246    /**
247     * @return The is the full duration of the media item (not trimmed)
248     */
249    public abstract long getDuration();
250
251    /**
252     * @return The source file type
253     */
254    public abstract int getFileType();
255
256    /**
257     * @return Get the native width of the media item
258     */
259    public abstract int getWidth();
260
261    /**
262     * @return Get the native height of the media item
263     */
264    public abstract int getHeight();
265
266    /**
267     * Get aspect ratio of the source media item.
268     *
269     * @return the aspect ratio as described in MediaProperties.
270     *         MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not
271     *         supported as in MediaProperties
272     */
273    public abstract int getAspectRatio();
274
275    /**
276     * Add the specified effect to this media item.
277     *
278     * Note that certain types of effects cannot be applied to video and to
279     * image media items. For example in certain implementation a Ken Burns
280     * implementation cannot be applied to video media item.
281     *
282     * This method invalidates transition video clips if the
283     * effect overlaps with the beginning and/or the end transition.
284     *
285     * @param effect The effect to apply
286     * @throws IllegalStateException if a preview or an export is in progress
287     * @throws IllegalArgumentException if the effect start and/or duration are
288     *      invalid or if the effect cannot be applied to this type of media
289     *      item or if the effect id is not unique across all the Effects
290     *      added.
291     */
292    public void addEffect(Effect effect) {
293
294        if (effect == null) {
295            throw new IllegalArgumentException("NULL effect cannot be applied");
296        }
297
298        if (effect.getMediaItem() != this) {
299            throw new IllegalArgumentException("Media item mismatch");
300        }
301
302        if (mEffects.contains(effect)) {
303            throw new IllegalArgumentException("Effect already exists: " + effect.getId());
304        }
305
306        if (effect.getStartTime() + effect.getDuration() > getDuration()) {
307            throw new IllegalArgumentException(
308            "Effect start time + effect duration > media clip duration");
309        }
310
311        mMANativeHelper.setGeneratePreview(true);
312
313        mEffects.add(effect);
314
315        invalidateTransitions(effect.getStartTime(), effect.getDuration());
316
317        if (effect instanceof EffectKenBurns) {
318            mRegenerateClip = true;
319        }
320    }
321
322    /**
323     * Remove the effect with the specified id.
324     *
325     * This method invalidates a transition video clip if the effect overlaps
326     * with a transition.
327     *
328     * @param effectId The id of the effect to be removed
329     *
330     * @return The effect that was removed
331     * @throws IllegalStateException if a preview or an export is in progress
332     */
333    public Effect removeEffect(String effectId) {
334        for (Effect effect : mEffects) {
335            if (effect.getId().equals(effectId)) {
336                mMANativeHelper.setGeneratePreview(true);
337
338                mEffects.remove(effect);
339
340                invalidateTransitions(effect.getStartTime(), effect.getDuration());
341                if (effect instanceof EffectKenBurns) {
342                    if (mGeneratedImageClip != null) {
343                        /**
344                         *  Delete the file
345                         */
346                        new File(mGeneratedImageClip).delete();
347                        /**
348                         *  Invalidate the filename
349                         */
350                        mGeneratedImageClip = null;
351                    }
352                    mRegenerateClip = false;
353                }
354                return effect;
355            }
356        }
357        return null;
358    }
359
360    /**
361     * Set the filepath of the generated image clip when the effect is added.
362     *
363     * @param The filepath of the generated image clip.
364     */
365    void setGeneratedImageClip(String generatedFilePath) {
366        mGeneratedImageClip = generatedFilePath;
367    }
368
369    /**
370     * Get the filepath of the generated image clip when the effect is added.
371     *
372     * @return The filepath of the generated image clip (null if it does not
373     *         exist)
374     */
375    String getGeneratedImageClip() {
376        return mGeneratedImageClip;
377    }
378
379    /**
380     * Find the effect with the specified id
381     *
382     * @param effectId The effect id
383     * @return The effect with the specified id (null if it does not exist)
384     */
385    public Effect getEffect(String effectId) {
386        for (Effect effect : mEffects) {
387            if (effect.getId().equals(effectId)) {
388                return effect;
389            }
390        }
391        return null;
392    }
393
394    /**
395     * Get the list of effects.
396     *
397     * @return the effects list. If no effects exist an empty list will be
398     *         returned.
399     */
400    public List<Effect> getAllEffects() {
401        return mEffects;
402    }
403
404    /**
405     * Add an overlay to the storyboard. This method invalidates a transition
406     * video clip if the overlay overlaps with a transition.
407     *
408     * @param overlay The overlay to add
409     * @throws IllegalStateException if a preview or an export is in progress or
410     *             if the overlay id is not unique across all the overlays added
411     *             or if the bitmap is not specified or if the dimensions of the
412     *             bitmap do not match the dimensions of the media item
413     * @throws FileNotFoundException, IOException if overlay could not be saved
414     *             to project path
415     */
416    public void addOverlay(Overlay overlay) throws FileNotFoundException, IOException {
417        if (overlay == null) {
418            throw new IllegalArgumentException("NULL Overlay cannot be applied");
419        }
420
421        if (overlay.getMediaItem() != this) {
422            throw new IllegalArgumentException("Media item mismatch");
423        }
424
425        if (mOverlays.contains(overlay)) {
426            throw new IllegalArgumentException("Overlay already exists: " + overlay.getId());
427        }
428
429        if (overlay.getStartTime() + overlay.getDuration() > getDuration()) {
430            throw new IllegalArgumentException(
431            "Overlay start time + overlay duration > media clip duration");
432        }
433
434        if (overlay instanceof OverlayFrame) {
435            final OverlayFrame frame = (OverlayFrame)overlay;
436            final Bitmap bitmap = frame.getBitmap();
437            if (bitmap == null) {
438                throw new IllegalArgumentException("Overlay bitmap not specified");
439            }
440
441            final int scaledWidth, scaledHeight;
442            if (this instanceof MediaVideoItem) {
443                scaledWidth = getWidth();
444                scaledHeight = getHeight();
445            } else {
446                scaledWidth = ((MediaImageItem)this).getScaledWidth();
447                scaledHeight = ((MediaImageItem)this).getScaledHeight();
448            }
449
450            /**
451             * The dimensions of the overlay bitmap must be the same as the
452             * media item dimensions
453             */
454            if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) {
455                throw new IllegalArgumentException(
456                "Bitmap dimensions must match media item dimensions");
457            }
458
459            mMANativeHelper.setGeneratePreview(true);
460            ((OverlayFrame)overlay).save(mProjectPath);
461
462            mOverlays.add(overlay);
463            invalidateTransitions(overlay.getStartTime(), overlay.getDuration());
464
465        } else {
466            throw new IllegalArgumentException("Overlay not supported");
467        }
468    }
469
470    /**
471     * @param flag The flag to indicate if regeneration of clip is true or
472     *            false.
473     */
474    void setRegenerateClip(boolean flag) {
475        mRegenerateClip = flag;
476    }
477
478    /**
479     * @return flag The flag to indicate if regeneration of clip is true or
480     *         false.
481     */
482    boolean getRegenerateClip() {
483        return mRegenerateClip;
484    }
485
486    /**
487     * Remove the overlay with the specified id.
488     *
489     * This method invalidates a transition video clip if the overlay overlaps
490     * with a transition.
491     *
492     * @param overlayId The id of the overlay to be removed
493     *
494     * @return The overlay that was removed
495     * @throws IllegalStateException if a preview or an export is in progress
496     */
497    public Overlay removeOverlay(String overlayId) {
498        for (Overlay overlay : mOverlays) {
499            if (overlay.getId().equals(overlayId)) {
500                mMANativeHelper.setGeneratePreview(true);
501
502                mOverlays.remove(overlay);
503                if (overlay instanceof OverlayFrame) {
504                    ((OverlayFrame)overlay).invalidate();
505                }
506                invalidateTransitions(overlay.getStartTime(), overlay.getDuration());
507                return overlay;
508            }
509        }
510        return null;
511    }
512
513    /**
514     * Find the overlay with the specified id
515     *
516     * @param overlayId The overlay id
517     *
518     * @return The overlay with the specified id (null if it does not exist)
519     */
520    public Overlay getOverlay(String overlayId) {
521        for (Overlay overlay : mOverlays) {
522            if (overlay.getId().equals(overlayId)) {
523                return overlay;
524            }
525        }
526
527        return null;
528    }
529
530    /**
531     * Get the list of overlays associated with this media item
532     *
533     * Note that if any overlay source files are not accessible anymore,
534     * this method will still provide the full list of overlays.
535     *
536     * @return The list of overlays. If no overlays exist an empty list will
537     *         be returned.
538     */
539    public List<Overlay> getAllOverlays() {
540        return mOverlays;
541    }
542
543    /**
544     * Create a thumbnail at specified time in a video stream in Bitmap format
545     *
546     * @param width width of the thumbnail in pixels
547     * @param height height of the thumbnail in pixels
548     * @param timeMs The time in the source video file at which the thumbnail is
549     *            requested (even if trimmed).
550     *
551     * @return The thumbnail as a Bitmap.
552     *
553     * @throws IOException if a file error occurs
554     * @throws IllegalArgumentException if time is out of video duration
555     */
556    public abstract Bitmap getThumbnail(int width, int height, long timeMs)
557                                        throws IOException;
558
559    /**
560     * Get the array of Bitmap thumbnails between start and end.
561     *
562     * @param width width of the thumbnail in pixels
563     * @param height height of the thumbnail in pixels
564     * @param startMs The start of time range in milliseconds
565     * @param endMs The end of the time range in milliseconds
566     * @param thumbnailCount The thumbnail count
567     * @param indices The indices of the thumbnails wanted
568     * @param callback The callback used to pass back the bitmaps
569     *
570     * @throws IOException if a file error occurs
571     */
572    public abstract void getThumbnailList(int width, int height,
573                                          long startMs, long endMs,
574                                          int thumbnailCount,
575                                          int[] indices,
576                                          GetThumbnailListCallback callback)
577                                          throws IOException;
578
579    public interface GetThumbnailListCallback {
580        public void onThumbnail(Bitmap bitmap, int index);
581    }
582
583    // This is for compatibility, only used in tests.
584    public Bitmap[] getThumbnailList(int width, int height,
585                                     long startMs, long endMs,
586                                     int thumbnailCount)
587                                     throws IOException {
588        final Bitmap[] bitmaps = new Bitmap[thumbnailCount];
589        int[] indices = new int[thumbnailCount];
590        for (int i = 0; i < thumbnailCount; i++) {
591            indices[i] = i;
592        }
593        getThumbnailList(width, height, startMs, endMs,
594                thumbnailCount, indices, new GetThumbnailListCallback() {
595            public void onThumbnail(Bitmap bitmap, int index) {
596                bitmaps[index] = bitmap;
597            }
598        });
599
600        return bitmaps;
601    }
602
603    /*
604     * {@inheritDoc}
605     */
606    @Override
607    public boolean equals(Object object) {
608        if (!(object instanceof MediaItem)) {
609            return false;
610        }
611        return mUniqueId.equals(((MediaItem)object).mUniqueId);
612    }
613
614    /*
615     * {@inheritDoc}
616     */
617    @Override
618    public int hashCode() {
619        return mUniqueId.hashCode();
620    }
621
622    /**
623     * Invalidate the start and end transitions if necessary
624     *
625     * @param startTimeMs The start time of the effect or overlay
626     * @param durationMs The duration of the effect or overlay
627     */
628    abstract void invalidateTransitions(long startTimeMs, long durationMs);
629
630    /**
631     * Invalidate the start and end transitions if necessary. This method is
632     * typically called when the start time and/or duration of an overlay or
633     * effect is changing.
634     *
635     * @param oldStartTimeMs The old start time of the effect or overlay
636     * @param oldDurationMs The old duration of the effect or overlay
637     * @param newStartTimeMs The new start time of the effect or overlay
638     * @param newDurationMs The new duration of the effect or overlay
639     */
640    abstract void invalidateTransitions(long oldStartTimeMs, long oldDurationMs,
641            long newStartTimeMs, long newDurationMs);
642
643    /**
644     * Check if two items overlap in time
645     *
646     * @param startTimeMs1 Item 1 start time
647     * @param durationMs1 Item 1 duration
648     * @param startTimeMs2 Item 2 start time
649     * @param durationMs2 Item 2 end time
650     * @return true if the two items overlap
651     */
652    protected boolean isOverlapping(long startTimeMs1, long durationMs1,
653                                    long startTimeMs2, long durationMs2) {
654        if (startTimeMs1 + durationMs1 <= startTimeMs2) {
655            return false;
656        } else if (startTimeMs1 >= startTimeMs2 + durationMs2) {
657            return false;
658        }
659
660        return true;
661    }
662
663    /**
664     * Adjust the duration transitions.
665     */
666    protected void adjustTransitions() {
667        /**
668         *  Check if the duration of transitions need to be adjusted
669         */
670        if (mBeginTransition != null) {
671            final long maxDurationMs = mBeginTransition.getMaximumDuration();
672            if (mBeginTransition.getDuration() > maxDurationMs) {
673                mBeginTransition.setDuration(maxDurationMs);
674            }
675        }
676
677        if (mEndTransition != null) {
678            final long maxDurationMs = mEndTransition.getMaximumDuration();
679            if (mEndTransition.getDuration() > maxDurationMs) {
680                mEndTransition.setDuration(maxDurationMs);
681            }
682        }
683    }
684
685    /**
686     * @return MediaArtistNativeHleper context
687     */
688    MediaArtistNativeHelper getNativeContext() {
689        return mMANativeHelper;
690    }
691
692    /**
693     * Initialises ClipSettings fields to default value
694     *
695     * @param ClipSettings object
696     *{@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings}
697     */
698    void initClipSettings(ClipSettings clipSettings) {
699        clipSettings.clipPath = null;
700        clipSettings.clipDecodedPath = null;
701        clipSettings.clipOriginalPath = null;
702        clipSettings.fileType = 0;
703        clipSettings.endCutTime = 0;
704        clipSettings.beginCutTime = 0;
705        clipSettings.beginCutPercent = 0;
706        clipSettings.endCutPercent = 0;
707        clipSettings.panZoomEnabled = false;
708        clipSettings.panZoomPercentStart = 0;
709        clipSettings.panZoomTopLeftXStart = 0;
710        clipSettings.panZoomTopLeftYStart = 0;
711        clipSettings.panZoomPercentEnd = 0;
712        clipSettings.panZoomTopLeftXEnd = 0;
713        clipSettings.panZoomTopLeftYEnd = 0;
714        clipSettings.mediaRendering = 0;
715        clipSettings.rgbWidth = 0;
716        clipSettings.rgbHeight = 0;
717    }
718
719    /**
720     * @return ClipSettings object with populated data
721     *{@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings}
722     */
723    ClipSettings getClipSettings() {
724        MediaVideoItem mVI = null;
725        MediaImageItem mII = null;
726        ClipSettings clipSettings = new ClipSettings();
727        initClipSettings(clipSettings);
728        if (this instanceof MediaVideoItem) {
729            mVI = (MediaVideoItem)this;
730            clipSettings.clipPath = mVI.getFilename();
731            clipSettings.fileType = mMANativeHelper.getMediaItemFileType(mVI.
732                                                                 getFileType());
733            clipSettings.beginCutTime = (int)mVI.getBoundaryBeginTime();
734            clipSettings.endCutTime = (int)mVI.getBoundaryEndTime();
735            clipSettings.mediaRendering = mMANativeHelper.
736                                          getMediaItemRenderingMode(mVI
737                                          .getRenderingMode());
738        } else if (this instanceof MediaImageItem) {
739            mII = (MediaImageItem)this;
740            clipSettings = mII.getImageClipProperties();
741        }
742        return clipSettings;
743    }
744
745    /**
746     * Generates a black frame to be used for generating
747     * begin transition at first media item in storyboard
748     * or end transition at last media item in storyboard
749     *
750     * @param ClipSettings object
751     *{@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings}
752     */
753    void generateBlankFrame(ClipSettings clipSettings) {
754        if (!mBlankFrameGenerated) {
755            int mWidth = 64;
756            int mHeight = 64;
757            mBlankFrameFilename = String.format(mProjectPath + "/" + "ghost.rgb");
758            FileOutputStream fl = null;
759            try {
760                 fl = new FileOutputStream(mBlankFrameFilename);
761            } catch (IOException e) {
762                /* catch IO exception */
763            }
764            final DataOutputStream dos = new DataOutputStream(fl);
765
766            final int [] framingBuffer = new int[mWidth];
767
768            ByteBuffer byteBuffer = ByteBuffer.allocate(framingBuffer.length * 4);
769            IntBuffer intBuffer;
770
771            byte[] array = byteBuffer.array();
772            int tmp = 0;
773            while(tmp < mHeight) {
774                intBuffer = byteBuffer.asIntBuffer();
775                intBuffer.put(framingBuffer,0,mWidth);
776                try {
777                    dos.write(array);
778                } catch (IOException e) {
779                    /* catch file write error */
780                }
781                tmp += 1;
782            }
783
784            try {
785                fl.close();
786            } catch (IOException e) {
787                /* file close error */
788            }
789            mBlankFrameGenerated = true;
790        }
791
792        clipSettings.clipPath = mBlankFrameFilename;
793        clipSettings.fileType = FileType.JPG;
794        clipSettings.beginCutTime = 0;
795        clipSettings.endCutTime = 0;
796        clipSettings.mediaRendering = MediaRendering.RESIZING;
797
798        clipSettings.rgbWidth = 64;
799        clipSettings.rgbHeight = 64;
800    }
801
802    /**
803     * Invalidates the blank frame generated
804     */
805    void invalidateBlankFrame() {
806        if (mBlankFrameFilename != null) {
807            if (new File(mBlankFrameFilename).exists()) {
808                new File(mBlankFrameFilename).delete();
809                mBlankFrameFilename = null;
810            }
811        }
812    }
813
814}
815