1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package android.media.videoeditor; 19 20 import java.io.File; 21 import java.io.FileNotFoundException; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.util.ArrayList; 25 import java.util.List; 26 27 import java.io.DataOutputStream; 28 import java.nio.ByteBuffer; 29 import java.nio.IntBuffer; 30 31 import android.graphics.Bitmap; 32 import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings; 33 import android.media.videoeditor.MediaArtistNativeHelper.FileType; 34 import 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 */ 41 public 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