MediaVideoItem.java revision 4b66f7a53f1b5a77c3ca1c12f256cdef078c1799
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 17package android.media.videoeditor; 18 19import java.io.IOException; 20import java.util.List; 21 22import android.graphics.Bitmap; 23import android.media.MediaRecorder; 24import android.util.Log; 25import android.view.SurfaceHolder; 26 27/** 28 * This class represents a video clip item on the storyboard 29 * {@hide} 30 */ 31public class MediaVideoItem extends MediaItem { 32 // Logging 33 private static final String TAG = "MediaVideoItem"; 34 35 // Instance variables 36 private final int mWidth; 37 private final int mHeight; 38 private final int mAspectRatio; 39 private final int mFileType; 40 private final int mVideoType; 41 private final int mVideoProfile; 42 private final int mVideoBitrate; 43 private final long mDurationMs; 44 private final int mAudioBitrate; 45 private final int mFps; 46 private final int mAudioType; 47 private final int mAudioChannels; 48 private final int mAudioSamplingFrequency; 49 50 private long mBeginBoundaryTimeMs; 51 private long mEndBoundaryTimeMs; 52 private int mVolumePercentage; 53 private String mAudioWaveformFilename; 54 private PlaybackThread mPlaybackThread; 55 56 /** 57 * This listener interface is used by the MediaVideoItem to emit playback 58 * progress notifications. This callback should be invoked after the 59 * number of frames specified by 60 * {@link #startPlayback(SurfaceHolder surfaceHolder, long fromMs, 61 * int callbackAfterFrameCount, PlaybackProgressListener listener)} 62 */ 63 public interface PlaybackProgressListener { 64 /** 65 * This method notifies the listener of the current time position while 66 * playing a media item 67 * 68 * @param mediaItem The media item 69 * @param timeMs The current playback position (expressed in milliseconds 70 * since the beginning of the media item). 71 * @param end true if the end of the media item was reached 72 */ 73 public void onProgress(MediaVideoItem mediaItem, long timeMs, boolean end); 74 } 75 76 /** 77 * The playback thread 78 */ 79 private class PlaybackThread extends Thread { 80 // Instance variables 81 private final static long FRAME_DURATION = 33; 82 private final PlaybackProgressListener mListener; 83 private final int mCallbackAfterFrameCount; 84 private final long mFromMs, mToMs; 85 private boolean mRun, mLoop; 86 private long mPositionMs; 87 88 /** 89 * Constructor 90 * 91 * @param fromMs The time (relative to the beginning of the media item) 92 * at which the playback will start 93 * @param toMs The time (relative to the beginning of the media item) at 94 * which the playback will stop. Use -1 to play to the end of 95 * the media item 96 * @param loop true if the playback should be looped once it reaches the 97 * end 98 * @param callbackAfterFrameCount The listener interface should be 99 * invoked after the number of frames specified by this 100 * parameter. 101 * @param listener The listener which will be notified of the playback 102 * progress 103 */ 104 public PlaybackThread(long fromMs, long toMs, boolean loop, int callbackAfterFrameCount, 105 PlaybackProgressListener listener) { 106 mPositionMs = mFromMs = fromMs; 107 if (toMs < 0) { 108 mToMs = mDurationMs; 109 } else { 110 mToMs = toMs; 111 } 112 mLoop = loop; 113 mCallbackAfterFrameCount = callbackAfterFrameCount; 114 mListener = listener; 115 mRun = true; 116 } 117 118 /* 119 * {@inheritDoc} 120 */ 121 @Override 122 public void run() { 123 if (Log.isLoggable(TAG, Log.DEBUG)) { 124 Log.d(TAG, "===> PlaybackThread.run enter"); 125 } 126 int frameCount = 0; 127 while (mRun) { 128 try { 129 sleep(FRAME_DURATION); 130 } catch (InterruptedException ex) { 131 break; 132 } 133 frameCount++; 134 mPositionMs += FRAME_DURATION; 135 136 if (mPositionMs >= mToMs) { 137 if (!mLoop) { 138 if (mListener != null) { 139 mListener.onProgress(MediaVideoItem.this, mPositionMs, true); 140 } 141 if (Log.isLoggable(TAG, Log.DEBUG)) { 142 Log.d(TAG, "PlaybackThread.run playback complete"); 143 } 144 break; 145 } else { 146 // Fire a notification for the end of the clip 147 if (mListener != null) { 148 mListener.onProgress(MediaVideoItem.this, mToMs, false); 149 } 150 151 // Rewind 152 mPositionMs = mFromMs; 153 if (mListener != null) { 154 mListener.onProgress(MediaVideoItem.this, mPositionMs, false); 155 } 156 if (Log.isLoggable(TAG, Log.DEBUG)) { 157 Log.d(TAG, "PlaybackThread.run playback complete"); 158 } 159 frameCount = 0; 160 } 161 } else { 162 if (frameCount == mCallbackAfterFrameCount) { 163 if (mListener != null) { 164 mListener.onProgress(MediaVideoItem.this, mPositionMs, false); 165 } 166 frameCount = 0; 167 } 168 } 169 } 170 if (Log.isLoggable(TAG, Log.DEBUG)) { 171 Log.d(TAG, "===> PlaybackThread.run exit"); 172 } 173 } 174 175 /** 176 * Stop the playback 177 * 178 * @return The stop position 179 */ 180 public long stopPlayback() { 181 mRun = false; 182 try { 183 join(); 184 } catch (InterruptedException ex) { 185 } 186 return mPositionMs; 187 } 188 }; 189 190 /** 191 * An object of this type cannot be instantiated with a default constructor 192 */ 193 @SuppressWarnings("unused") 194 private MediaVideoItem() throws IOException { 195 this(null, null, RENDERING_MODE_BLACK_BORDER); 196 } 197 198 /** 199 * Constructor 200 * 201 * @param mediaItemId The MediaItem id 202 * @param filename The image file name 203 * @param renderingMode The rendering mode 204 * 205 * @throws IOException if the file cannot be opened for reading 206 */ 207 public MediaVideoItem(String mediaItemId, String filename, int renderingMode) 208 throws IOException { 209 this(mediaItemId, filename, renderingMode, null); 210 } 211 212 /** 213 * Constructor 214 * 215 * @param mediaItemId The MediaItem id 216 * @param filename The image file name 217 * @param renderingMode The rendering mode 218 * @param audioWaveformFilename The name of the audio waveform file 219 * 220 * @throws IOException if the file cannot be opened for reading 221 */ 222 MediaVideoItem(String mediaItemId, String filename, int renderingMode, 223 String audioWaveformFilename) throws IOException { 224 super(mediaItemId, filename, renderingMode); 225 // TODO: Set these variables correctly 226 mWidth = 1080; 227 mHeight = 720; 228 mAspectRatio = MediaProperties.ASPECT_RATIO_3_2; 229 mFileType = MediaProperties.FILE_MP4; 230 mVideoType = MediaRecorder.VideoEncoder.H264; 231 // Do we have predefined values for this variable? 232 mVideoProfile = 0; 233 // Can video and audio duration be different? 234 mDurationMs = 10000; 235 mVideoBitrate = 800000; 236 mAudioBitrate = 30000; 237 mFps = 30; 238 mAudioType = MediaProperties.ACODEC_AAC_LC; 239 mAudioChannels = 2; 240 mAudioSamplingFrequency = 16000; 241 242 mBeginBoundaryTimeMs = 0; 243 mEndBoundaryTimeMs = mDurationMs; 244 mVolumePercentage = 100; 245 mAudioWaveformFilename = audioWaveformFilename; 246 } 247 248 /** 249 * Sets the start and end marks for trimming a video media item. 250 * This method will adjust the duration of bounding transitions if the 251 * current duration of the transactions become greater than the maximum 252 * allowable duration. 253 * 254 * @param beginMs Start time in milliseconds. Set to 0 to extract from the 255 * beginning 256 * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to 257 * extract until the end 258 * 259 * @throws IllegalArgumentException if the start time is greater or equal than 260 * end time, the end time is beyond the file duration, the start time 261 * is negative 262 */ 263 public void setExtractBoundaries(long beginMs, long endMs) { 264 if (beginMs > mDurationMs) { 265 throw new IllegalArgumentException("Invalid start time"); 266 } 267 if (endMs > mDurationMs) { 268 throw new IllegalArgumentException("Invalid end time"); 269 } 270 271 if (beginMs != mBeginBoundaryTimeMs) { 272 if (mBeginTransition != null) { 273 mBeginTransition.invalidate(); 274 } 275 } 276 277 if (endMs != mEndBoundaryTimeMs) { 278 if (mEndTransition != null) { 279 mEndTransition.invalidate(); 280 } 281 } 282 283 mBeginBoundaryTimeMs = beginMs; 284 mEndBoundaryTimeMs = endMs; 285 286 // Check if the duration of transitions need to be adjusted 287 if (mBeginTransition != null) { 288 final long maxDurationMs = mBeginTransition.getMaximumDuration(); 289 if (mBeginTransition.getDuration() > maxDurationMs) { 290 mBeginTransition.setDuration(maxDurationMs); 291 } 292 } 293 294 if (mEndTransition != null) { 295 final long maxDurationMs = mEndTransition.getMaximumDuration(); 296 if (mEndTransition.getDuration() > maxDurationMs) { 297 mEndTransition.setDuration(maxDurationMs); 298 } 299 } 300 301 final List<Overlay> overlays = getAllOverlays(); 302 for (Overlay overlay : overlays) { 303 // Adjust the start time if necessary 304 if (overlay.getStartTime() < mBeginBoundaryTimeMs) { 305 overlay.setStartTime(mBeginBoundaryTimeMs); 306 } 307 308 // Adjust the duration if necessary 309 if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) { 310 overlay.setDuration(getTimelineDuration() - overlay.getStartTime()); 311 } 312 } 313 314 final List<Effect> effects = getAllEffects(); 315 for (Effect effect : effects) { 316 // Adjust the start time if necessary 317 if (effect.getStartTime() < mBeginBoundaryTimeMs) { 318 effect.setStartTime(mBeginBoundaryTimeMs); 319 } 320 321 // Adjust the duration if necessary 322 if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) { 323 effect.setDuration(getTimelineDuration() - effect.getStartTime()); 324 } 325 } 326 } 327 328 /** 329 * @return The boundary begin time 330 */ 331 public long getBoundaryBeginTime() { 332 return mBeginBoundaryTimeMs; 333 } 334 335 /** 336 * @return The boundary end time 337 */ 338 public long getBoundaryEndTime() { 339 return mEndBoundaryTimeMs; 340 } 341 342 /* 343 * {@inheritDoc} 344 */ 345 @Override 346 public void addEffect(Effect effect) { 347 if (effect instanceof EffectKenBurns) { 348 throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem"); 349 } 350 super.addEffect(effect); 351 } 352 353 /* 354 * {@inheritDoc} 355 */ 356 @Override 357 public Bitmap getThumbnail(int width, int height, long timeMs) { 358 return null; 359 } 360 361 /* 362 * {@inheritDoc} 363 */ 364 @Override 365 public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs, 366 int thumbnailCount) throws IOException { 367 return null; 368 } 369 370 /* 371 * {@inheritDoc} 372 */ 373 @Override 374 public int getAspectRatio() { 375 return mAspectRatio; 376 } 377 378 /* 379 * {@inheritDoc} 380 */ 381 @Override 382 public int getFileType() { 383 return mFileType; 384 } 385 386 /* 387 * {@inheritDoc} 388 */ 389 @Override 390 public int getWidth() { 391 return mWidth; 392 } 393 394 /* 395 * {@inheritDoc} 396 */ 397 @Override 398 public int getHeight() { 399 return mHeight; 400 } 401 402 /** 403 * @return The duration of the video clip 404 */ 405 public long getDuration() { 406 return mDurationMs; 407 } 408 409 /** 410 * @return The timeline duration. This is the actual duration in the 411 * timeline (trimmed duration) 412 */ 413 @Override 414 public long getTimelineDuration() { 415 return mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 416 } 417 418 /** 419 * Render a frame according to the playback (in the native aspect ratio) for 420 * the specified media item. All effects and overlays applied to the media 421 * item are ignored. The extract boundaries are also ignored. This method 422 * can be used to playback frames when implementing trimming functionality. 423 * 424 * @param surfaceHolder SurfaceHolder used by the application 425 * @param timeMs time corresponding to the frame to display (relative to the 426 * the beginning of the media item). 427 * @return The accurate time stamp of the frame that is rendered . 428 * @throws IllegalStateException if a playback, preview or an export is 429 * already in progress 430 * @throws IllegalArgumentException if time is negative or greater than the 431 * media item duration 432 */ 433 public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) { 434 return timeMs; 435 } 436 437 /** 438 * Start the playback of this media item. This method does not block (does 439 * not wait for the playback to complete). The PlaybackProgressListener 440 * allows to track the progress at the time interval determined by the 441 * callbackAfterFrameCount parameter. The SurfaceHolder has to be created 442 * and ready for use before calling this method. 443 * 444 * @param surfaceHolder SurfaceHolder where the frames are rendered. 445 * @param fromMs The time (relative to the beginning of the media item) at 446 * which the playback will start 447 * @param toMs The time (relative to the beginning of the media item) at 448 * which the playback will stop. Use -1 to play to the end of the 449 * media item 450 * @param loop true if the playback should be looped once it reaches the end 451 * @param callbackAfterFrameCount The listener interface should be invoked 452 * after the number of frames specified by this parameter. 453 * @param listener The listener which will be notified of the playback 454 * progress 455 * @throws IllegalArgumentException if fromMs or toMs is beyond the playback 456 * duration 457 * @throws IllegalStateException if a playback, preview or an export is 458 * already in progress 459 */ 460 public void startPlayback(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop, 461 int callbackAfterFrameCount, PlaybackProgressListener listener) { 462 if (fromMs >= mDurationMs) { 463 return; 464 } 465 mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, callbackAfterFrameCount, 466 listener); 467 mPlaybackThread.start(); 468 } 469 470 /** 471 * Stop the media item playback. This method blocks until the ongoing 472 * playback is stopped. 473 * 474 * @return The accurate current time when stop is effective expressed in 475 * milliseconds 476 */ 477 public long stopPlayback() { 478 final long stopTimeMs; 479 if (mPlaybackThread != null) { 480 stopTimeMs = mPlaybackThread.stopPlayback(); 481 mPlaybackThread = null; 482 } else { 483 stopTimeMs = 0; 484 } 485 return stopTimeMs; 486 } 487 488 /** 489 * This API allows to generate a file containing the sample volume levels of 490 * the Audio track of this media item. This function may take significant 491 * time and is blocking. The file can be retrieved using 492 * getAudioWaveformFilename(). 493 * 494 * @param listener The progress listener 495 * 496 * @throws IOException if the output file cannot be created 497 * @throws IllegalArgumentException if the mediaItem does not have a valid 498 * Audio track 499 */ 500 public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 501 throws IOException { 502 // TODO: Set mAudioWaveformFilename at the end once the export is complete 503 } 504 505 /** 506 * Get the audio waveform file name if {@link #extractAudioWaveform()} was 507 * successful. The file format is as following: 508 * <ul> 509 * <li>first 4 bytes provide the number of samples for each value, as big-endian signed</li> 510 * <li>4 following bytes is the total number of values in the file, as big-endian signed</li> 511 * <li>all values follow as bytes Name is unique.</li> 512 *</ul> 513 * @return the name of the file, null if the file has not been computed or 514 * if there is no Audio track in the mediaItem 515 */ 516 public String getAudioWaveformFilename() { 517 return mAudioWaveformFilename; 518 } 519 520 /** 521 * Set volume of the Audio track of this mediaItem 522 * 523 * @param volumePercent in %/. 100% means no change; 50% means half value, 200% 524 * means double, 0% means silent. 525 * @throws UsupportedOperationException if volume value is not supported 526 */ 527 public void setVolume(int volumePercent) { 528 mVolumePercentage = volumePercent; 529 } 530 531 /** 532 * Get the volume value of the audio track as percentage. Call of this 533 * method before calling setVolume will always return 100% 534 * 535 * @return the volume in percentage 536 */ 537 public int getVolume() { 538 return mVolumePercentage; 539 } 540 541 /** 542 * @return The video type 543 */ 544 public int getVideoType() { 545 return mVideoType; 546 } 547 548 /** 549 * @return The video profile 550 */ 551 public int getVideoProfile() { 552 return mVideoProfile; 553 } 554 555 /** 556 * @return The video bitrate 557 */ 558 public int getVideoBitrate() { 559 return mVideoBitrate; 560 } 561 562 /** 563 * @return The audio bitrate 564 */ 565 public int getAudioBitrate() { 566 return mAudioBitrate; 567 } 568 569 /** 570 * @return The number of frames per second 571 */ 572 public int getFps() { 573 return mFps; 574 } 575 576 /** 577 * @return The audio codec 578 */ 579 public int getAudioType() { 580 return mAudioType; 581 } 582 583 /** 584 * @return The number of audio channels 585 */ 586 public int getAudioChannels() { 587 return mAudioChannels; 588 } 589 590 /** 591 * @return The audio sample frequency 592 */ 593 public int getAudioSamplingFrequency() { 594 return mAudioSamplingFrequency; 595 } 596} 597