MediaVideoItem.java revision 3ced044154945f9d60983032278e00fe28f4ab1b
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.IOException; 22import java.lang.ref.SoftReference; 23import android.graphics.Bitmap; 24import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings; 25import android.media.videoeditor.MediaArtistNativeHelper.Properties; 26import android.media.videoeditor.VideoEditorProfile; 27import android.view.Surface; 28import android.view.SurfaceHolder; 29 30/** 31 * This class represents a video clip item on the storyboard 32 * {@hide} 33 */ 34public class MediaVideoItem extends MediaItem { 35 36 /** 37 * Instance variables 38 */ 39 private final int mWidth; 40 private final int mHeight; 41 private final int mAspectRatio; 42 private final int mFileType; 43 private final int mVideoType; 44 private final int mVideoProfile; 45 private final int mVideoLevel; 46 private final int mVideoBitrate; 47 private final long mDurationMs; 48 private final int mAudioBitrate; 49 private final int mFps; 50 private final int mAudioType; 51 private final int mAudioChannels; 52 private final int mAudioSamplingFrequency; 53 private long mBeginBoundaryTimeMs; 54 private long mEndBoundaryTimeMs; 55 private int mVolumePercentage; 56 private boolean mMuted; 57 private String mAudioWaveformFilename; 58 private MediaArtistNativeHelper mMANativeHelper; 59 private VideoEditorImpl mVideoEditor; 60 /** 61 * The audio waveform data 62 */ 63 private SoftReference<WaveformData> mWaveformData; 64 65 /** 66 * An object of this type cannot be instantiated with a default constructor 67 */ 68 @SuppressWarnings("unused") 69 private MediaVideoItem() throws IOException { 70 this(null, null, null, RENDERING_MODE_BLACK_BORDER); 71 } 72 73 /** 74 * Constructor 75 * 76 * @param editor The video editor reference 77 * @param mediaItemId The MediaItem id 78 * @param filename The image file name 79 * @param renderingMode The rendering mode 80 * 81 * @throws IOException if the file cannot be opened for reading 82 */ 83 public MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, 84 int renderingMode) throws IOException { 85 this(editor, mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null); 86 } 87 88 /** 89 * Constructor 90 * 91 * @param editor The video editor reference 92 * @param mediaItemId The MediaItem id 93 * @param filename The image file name 94 * @param renderingMode The rendering mode 95 * @param beginMs Start time in milliseconds. Set to 0 to extract from the 96 * beginning 97 * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to 98 * extract until the end 99 * @param volumePercent in %/. 100% means no change; 50% means half value, 200% 100 * means double, 0% means silent. 101 * @param muted true if the audio is muted 102 * @param audioWaveformFilename The name of the audio waveform file 103 * 104 * @throws IOException if the file cannot be opened for reading 105 */ 106 MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, 107 int renderingMode, long beginMs, long endMs, int volumePercent, boolean muted, 108 String audioWaveformFilename) throws IOException { 109 super(editor, mediaItemId, filename, renderingMode); 110 111 if (editor instanceof VideoEditorImpl) { 112 mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext(); 113 mVideoEditor = ((VideoEditorImpl)editor); 114 } 115 116 final Properties properties; 117 try { 118 properties = mMANativeHelper.getMediaProperties(filename); 119 } catch ( Exception e) { 120 throw new IllegalArgumentException(e.getMessage() + " : " + filename); 121 } 122 123 /** Check the platform specific maximum import resolution */ 124 VideoEditorProfile veProfile = VideoEditorProfile.get(); 125 if (veProfile == null) { 126 throw new RuntimeException("Can't get the video editor profile"); 127 } 128 final int maxInputWidth = veProfile.maxInputVideoFrameWidth; 129 final int maxInputHeight = veProfile.maxInputVideoFrameHeight; 130 if ((properties.width > maxInputWidth) || 131 (properties.height > maxInputHeight)) { 132 throw new IllegalArgumentException( 133 "Unsupported import resolution. Supported maximum width:" + 134 maxInputWidth + " height:" + maxInputHeight + 135 ", current width:" + properties.width + 136 " height:" + properties.height); 137 } 138 /** Check the platform specific maximum video profile and level */ 139 if (!properties.profileSupported) { 140 throw new IllegalArgumentException( 141 "Unsupported video profile " + properties.profile); 142 } 143 if (!properties.levelSupported) { 144 throw new IllegalArgumentException( 145 "Unsupported video level " + properties.level); 146 } 147 switch (mMANativeHelper.getFileType(properties.fileType)) { 148 case MediaProperties.FILE_3GP: 149 case MediaProperties.FILE_MP4: 150 case MediaProperties.FILE_M4V: 151 break; 152 153 default: 154 throw new IllegalArgumentException("Unsupported Input File Type"); 155 } 156 157 switch (mMANativeHelper.getVideoCodecType(properties.videoFormat)) { 158 case MediaProperties.VCODEC_H263: 159 case MediaProperties.VCODEC_H264: 160 case MediaProperties.VCODEC_MPEG4: 161 break; 162 163 default: 164 throw new IllegalArgumentException("Unsupported Video Codec Format in Input File"); 165 } 166 167 mWidth = properties.width; 168 mHeight = properties.height; 169 mAspectRatio = mMANativeHelper.getAspectRatio(properties.width, 170 properties.height); 171 mFileType = mMANativeHelper.getFileType(properties.fileType); 172 mVideoType = mMANativeHelper.getVideoCodecType(properties.videoFormat); 173 mVideoProfile = properties.profile; 174 mVideoLevel = properties.level; 175 mDurationMs = properties.videoDuration; 176 mVideoBitrate = properties.videoBitrate; 177 mAudioBitrate = properties.audioBitrate; 178 mFps = (int)properties.averageFrameRate; 179 mAudioType = mMANativeHelper.getAudioCodecType(properties.audioFormat); 180 mAudioChannels = properties.audioChannels; 181 mAudioSamplingFrequency = properties.audioSamplingFrequency; 182 mBeginBoundaryTimeMs = beginMs; 183 mEndBoundaryTimeMs = endMs == END_OF_FILE ? mDurationMs : endMs; 184 mVolumePercentage = volumePercent; 185 mMuted = muted; 186 mAudioWaveformFilename = audioWaveformFilename; 187 if (audioWaveformFilename != null) { 188 mWaveformData = new SoftReference<WaveformData>( 189 new WaveformData(audioWaveformFilename)); 190 } else { 191 mWaveformData = null; 192 } 193 } 194 195 /** 196 * Sets the start and end marks for trimming a video media item. 197 * This method will adjust the duration of bounding transitions, effects 198 * and overlays if the current duration of the transactions become greater 199 * than the maximum allowable duration. 200 * 201 * @param beginMs Start time in milliseconds. Set to 0 to extract from the 202 * beginning 203 * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to 204 * extract until the end 205 * 206 * @throws IllegalArgumentException if the start time is greater or equal than 207 * end time, the end time is beyond the file duration, the start time 208 * is negative 209 */ 210 public void setExtractBoundaries(long beginMs, long endMs) { 211 if (beginMs > mDurationMs) { 212 throw new IllegalArgumentException("setExtractBoundaries: Invalid start time"); 213 } 214 215 if (endMs > mDurationMs) { 216 throw new IllegalArgumentException("setExtractBoundaries: Invalid end time"); 217 } 218 219 if ((endMs != -1) && (beginMs >= endMs) ) { 220 throw new IllegalArgumentException("setExtractBoundaries: Start time is greater than end time"); 221 } 222 223 if ((beginMs < 0) || ((endMs != -1) && (endMs < 0))) { 224 throw new IllegalArgumentException("setExtractBoundaries: Start time or end time is negative"); 225 } 226 227 mMANativeHelper.setGeneratePreview(true); 228 229 if (beginMs != mBeginBoundaryTimeMs) { 230 if (mBeginTransition != null) { 231 mBeginTransition.invalidate(); 232 } 233 } 234 235 if (endMs != mEndBoundaryTimeMs) { 236 if (mEndTransition != null) { 237 mEndTransition.invalidate(); 238 } 239 } 240 241 mBeginBoundaryTimeMs = beginMs; 242 mEndBoundaryTimeMs = endMs; 243 adjustTransitions(); 244 mVideoEditor.updateTimelineDuration(); 245 /** 246 * Note that the start and duration of any effects and overlays are 247 * not adjusted nor are they automatically removed if they fall 248 * outside the new boundaries. 249 */ 250 } 251 252 /** 253 * @return The boundary begin time 254 */ 255 public long getBoundaryBeginTime() { 256 return mBeginBoundaryTimeMs; 257 } 258 259 /** 260 * @return The boundary end time 261 */ 262 public long getBoundaryEndTime() { 263 return mEndBoundaryTimeMs; 264 } 265 266 /* 267 * {@inheritDoc} 268 */ 269 @Override 270 public void addEffect(Effect effect) { 271 if (effect instanceof EffectKenBurns) { 272 throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem"); 273 } 274 super.addEffect(effect); 275 } 276 277 /* 278 * {@inheritDoc} 279 */ 280 @Override 281 public Bitmap getThumbnail(int width, int height, long timeMs) { 282 if (timeMs > mDurationMs) { 283 throw new IllegalArgumentException("Time Exceeds duration"); 284 } 285 286 if (timeMs < 0) { 287 throw new IllegalArgumentException("Invalid Time duration"); 288 } 289 290 if ((width <= 0) || (height <= 0)) { 291 throw new IllegalArgumentException("Invalid Dimensions"); 292 } 293 294 return mMANativeHelper.getPixels(super.getFilename(), width, height,timeMs); 295 } 296 297 /* 298 * {@inheritDoc} 299 */ 300 @Override 301 public void getThumbnailList(int width, int height, 302 long startMs, long endMs, 303 int thumbnailCount, 304 int[] indices, 305 GetThumbnailListCallback callback) 306 throws IOException { 307 if (startMs > endMs) { 308 throw new IllegalArgumentException("Start time is greater than end time"); 309 } 310 311 if (endMs > mDurationMs) { 312 throw new IllegalArgumentException("End time is greater than file duration"); 313 } 314 315 if ((height <= 0) || (width <= 0)) { 316 throw new IllegalArgumentException("Invalid dimension"); 317 } 318 319 mMANativeHelper.getPixelsList(super.getFilename(), width, 320 height, startMs, endMs, thumbnailCount, indices, callback); 321 } 322 323 /* 324 * {@inheritDoc} 325 */ 326 @Override 327 void invalidateTransitions(long startTimeMs, long durationMs) { 328 /** 329 * Check if the item overlaps with the beginning and end transitions 330 */ 331 if (mBeginTransition != null) { 332 if (isOverlapping(startTimeMs, durationMs, 333 mBeginBoundaryTimeMs, mBeginTransition.getDuration())) { 334 mBeginTransition.invalidate(); 335 } 336 } 337 338 if (mEndTransition != null) { 339 final long transitionDurationMs = mEndTransition.getDuration(); 340 if (isOverlapping(startTimeMs, durationMs, 341 mEndBoundaryTimeMs - transitionDurationMs, transitionDurationMs)) { 342 mEndTransition.invalidate(); 343 } 344 } 345 } 346 347 /* 348 * {@inheritDoc} 349 */ 350 @Override 351 void invalidateTransitions(long oldStartTimeMs, long oldDurationMs, long newStartTimeMs, 352 long newDurationMs) { 353 /** 354 * Check if the item overlaps with the beginning and end transitions 355 */ 356 if (mBeginTransition != null) { 357 final long transitionDurationMs = mBeginTransition.getDuration(); 358 final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs, 359 mBeginBoundaryTimeMs, transitionDurationMs); 360 final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs, 361 mBeginBoundaryTimeMs, transitionDurationMs); 362 /** 363 * Invalidate transition if: 364 * 365 * 1. New item overlaps the transition, the old one did not 366 * 2. New item does not overlap the transition, the old one did 367 * 3. New and old item overlap the transition if begin or end 368 * time changed 369 */ 370 if (newOverlap != oldOverlap) { // Overlap has changed 371 mBeginTransition.invalidate(); 372 } else if (newOverlap) { // Both old and new overlap 373 if ((oldStartTimeMs != newStartTimeMs) || 374 !(oldStartTimeMs + oldDurationMs > transitionDurationMs && 375 newStartTimeMs + newDurationMs > transitionDurationMs)) { 376 mBeginTransition.invalidate(); 377 } 378 } 379 } 380 381 if (mEndTransition != null) { 382 final long transitionDurationMs = mEndTransition.getDuration(); 383 final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs, 384 mEndBoundaryTimeMs - transitionDurationMs, transitionDurationMs); 385 final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs, 386 mEndBoundaryTimeMs - transitionDurationMs, transitionDurationMs); 387 /** 388 * Invalidate transition if: 389 * 390 * 1. New item overlaps the transition, the old one did not 391 * 2. New item does not overlap the transition, the old one did 392 * 3. New and old item overlap the transition if begin or end 393 * time changed 394 */ 395 if (newOverlap != oldOverlap) { // Overlap has changed 396 mEndTransition.invalidate(); 397 } else if (newOverlap) { // Both old and new overlap 398 if ((oldStartTimeMs + oldDurationMs != newStartTimeMs + newDurationMs) || 399 ((oldStartTimeMs > mEndBoundaryTimeMs - transitionDurationMs) || 400 newStartTimeMs > mEndBoundaryTimeMs - transitionDurationMs)) { 401 mEndTransition.invalidate(); 402 } 403 } 404 } 405 } 406 407 /* 408 * {@inheritDoc} 409 */ 410 @Override 411 public int getAspectRatio() { 412 return mAspectRatio; 413 } 414 415 /* 416 * {@inheritDoc} 417 */ 418 @Override 419 public int getFileType() { 420 return mFileType; 421 } 422 423 /* 424 * {@inheritDoc} 425 */ 426 @Override 427 public int getWidth() { 428 return mWidth; 429 } 430 431 /* 432 * {@inheritDoc} 433 */ 434 @Override 435 public int getHeight() { 436 return mHeight; 437 } 438 439 /* 440 * {@inheritDoc} 441 */ 442 @Override 443 public long getDuration() { 444 return mDurationMs; 445 } 446 447 /* 448 * {@inheritDoc} 449 */ 450 @Override 451 public long getTimelineDuration() { 452 return mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 453 } 454 455 /** 456 * Render a frame according to the playback (in the native aspect ratio) for 457 * the specified media item. All effects and overlays applied to the media 458 * item are ignored. The extract boundaries are also ignored. This method 459 * can be used to playback frames when implementing trimming functionality. 460 * 461 * @param surfaceHolder SurfaceHolder used by the application 462 * @param timeMs time corresponding to the frame to display (relative to the 463 * the beginning of the media item). 464 * @return The accurate time stamp of the frame that is rendered . 465 * @throws IllegalStateException if a playback, preview or an export is 466 * already in progress 467 * @throws IllegalArgumentException if time is negative or greater than the 468 * media item duration 469 */ 470 public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) { 471 if (surfaceHolder == null) { 472 throw new IllegalArgumentException("Surface Holder is null"); 473 } 474 475 if (timeMs > mDurationMs || timeMs < 0) { 476 throw new IllegalArgumentException("requested time not correct"); 477 } 478 479 final Surface surface = surfaceHolder.getSurface(); 480 if (surface == null) { 481 throw new RuntimeException("Surface could not be retrieved from Surface holder"); 482 } 483 484 if (mFilename != null) { 485 return mMANativeHelper.renderMediaItemPreviewFrame(surface, 486 mFilename,timeMs,mWidth,mHeight); 487 } else { 488 return 0; 489 } 490 } 491 492 493 /** 494 * This API allows to generate a file containing the sample volume levels of 495 * the Audio track of this media item. This function may take significant 496 * time and is blocking. The file can be retrieved using 497 * getAudioWaveformFilename(). 498 * 499 * @param listener The progress listener 500 * 501 * @throws IOException if the output file cannot be created 502 * @throws IllegalArgumentException if the mediaItem does not have a valid 503 * Audio track 504 */ 505 public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 506 throws IOException { 507 int frameDuration = 0; 508 int sampleCount = 0; 509 final String projectPath = mMANativeHelper.getProjectPath(); 510 /** 511 * Waveform file does not exist 512 */ 513 if (mAudioWaveformFilename == null ) { 514 /** 515 * Since audioWaveformFilename will not be supplied,it is generated 516 */ 517 String mAudioWaveFileName = null; 518 519 mAudioWaveFileName = 520 String.format(projectPath + "/" + "audioWaveformFile-"+ getId() + ".dat"); 521 /** 522 * Logic to get frame duration = (no. of frames per sample * 1000)/ 523 * sampling frequency 524 */ 525 if (mMANativeHelper.getAudioCodecType(mAudioType) == 526 MediaProperties.ACODEC_AMRNB ) { 527 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB*1000)/ 528 MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 529 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB; 530 } else if (mMANativeHelper.getAudioCodecType(mAudioType) == 531 MediaProperties.ACODEC_AMRWB ) { 532 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000)/ 533 MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 534 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB; 535 } else if (mMANativeHelper.getAudioCodecType(mAudioType) == 536 MediaProperties.ACODEC_AAC_LC ) { 537 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000)/ 538 MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 539 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC; 540 } 541 542 mMANativeHelper.generateAudioGraph( getId(), 543 mFilename, 544 mAudioWaveFileName, 545 frameDuration, 546 MediaProperties.DEFAULT_CHANNEL_COUNT, 547 sampleCount, 548 listener, 549 true); 550 /** 551 * Record the generated file name 552 */ 553 mAudioWaveformFilename = mAudioWaveFileName; 554 } 555 mWaveformData = 556 new SoftReference<WaveformData>(new WaveformData(mAudioWaveformFilename)); 557 } 558 559 /** 560 * Get the audio waveform file name if {@link #extractAudioWaveform()} was 561 * successful. The file format is as following: 562 * <ul> 563 * <li>first 4 bytes provide the number of samples for each value, as big-endian signed</li> 564 * <li>4 following bytes is the total number of values in the file, as big-endian signed</li> 565 * <li>all values follow as bytes Name is unique.</li> 566 *</ul> 567 * @return the name of the file, null if the file has not been computed or 568 * if there is no Audio track in the mediaItem 569 */ 570 String getAudioWaveformFilename() { 571 return mAudioWaveformFilename; 572 } 573 574 /** 575 * Invalidate the AudioWaveform File 576 */ 577 void invalidate() { 578 if (mAudioWaveformFilename != null) { 579 new File(mAudioWaveformFilename).delete(); 580 mAudioWaveformFilename = null; 581 } 582 } 583 584 /** 585 * @return The waveform data 586 */ 587 public WaveformData getWaveformData() throws IOException { 588 if (mWaveformData == null) { 589 return null; 590 } 591 592 WaveformData waveformData = mWaveformData.get(); 593 if (waveformData != null) { 594 return waveformData; 595 } else if (mAudioWaveformFilename != null) { 596 try { 597 waveformData = new WaveformData(mAudioWaveformFilename); 598 } catch(IOException e) { 599 throw e; 600 } 601 mWaveformData = new SoftReference<WaveformData>(waveformData); 602 return waveformData; 603 } else { 604 return null; 605 } 606 } 607 608 /** 609 * Set volume of the Audio track of this mediaItem 610 * 611 * @param volumePercent in %/. 100% means no change; 50% means half value, 200% 612 * means double, 0% means silent. 613 * @throws UsupportedOperationException if volume value is not supported 614 */ 615 public void setVolume(int volumePercent) { 616 if ((volumePercent <0) || (volumePercent >100)) { 617 throw new IllegalArgumentException("Invalid volume"); 618 } 619 620 mVolumePercentage = volumePercent; 621 } 622 623 /** 624 * Get the volume value of the audio track as percentage. Call of this 625 * method before calling setVolume will always return 100% 626 * 627 * @return the volume in percentage 628 */ 629 public int getVolume() { 630 return mVolumePercentage; 631 } 632 633 /** 634 * @param muted true to mute the media item 635 */ 636 public void setMute(boolean muted) { 637 mMANativeHelper.setGeneratePreview(true); 638 mMuted = muted; 639 if (mBeginTransition != null) { 640 mBeginTransition.invalidate(); 641 } 642 if (mEndTransition != null) { 643 mEndTransition.invalidate(); 644 } 645 } 646 647 /** 648 * @return true if the media item is muted 649 */ 650 public boolean isMuted() { 651 return mMuted; 652 } 653 654 /** 655 * @return The video type 656 */ 657 public int getVideoType() { 658 return mVideoType; 659 } 660 661 /** 662 * @return The video profile 663 */ 664 public int getVideoProfile() { 665 return mVideoProfile; 666 } 667 668 /** 669 * @return The video profile 670 */ 671 public int getVideoLevel() { 672 return mVideoLevel; 673 } 674 675 /** 676 * @return The video bitrate 677 */ 678 public int getVideoBitrate() { 679 return mVideoBitrate; 680 } 681 682 /** 683 * @return The audio bitrate 684 */ 685 public int getAudioBitrate() { 686 return mAudioBitrate; 687 } 688 689 /** 690 * @return The number of frames per second 691 */ 692 public int getFps() { 693 return mFps; 694 } 695 696 /** 697 * @return The audio codec 698 */ 699 public int getAudioType() { 700 return mAudioType; 701 } 702 703 /** 704 * @return The number of audio channels 705 */ 706 public int getAudioChannels() { 707 return mAudioChannels; 708 } 709 710 /** 711 * @return The audio sample frequency 712 */ 713 public int getAudioSamplingFrequency() { 714 return mAudioSamplingFrequency; 715 } 716 717 /** 718 * @return The Video media item properties in ClipSettings class object 719 * {@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings} 720 */ 721 ClipSettings getVideoClipProperties() { 722 ClipSettings clipSettings = new ClipSettings(); 723 clipSettings.clipPath = getFilename(); 724 clipSettings.fileType = mMANativeHelper.getMediaItemFileType(getFileType()); 725 clipSettings.beginCutTime = (int)getBoundaryBeginTime(); 726 clipSettings.endCutTime = (int)getBoundaryEndTime(); 727 clipSettings.mediaRendering = mMANativeHelper.getMediaItemRenderingMode(getRenderingMode()); 728 729 return clipSettings; 730 } 731} 732