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