MediaVideoItem.java revision e5867ef3f096521c4a7a289d83e75904b3a977c5
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; 20 21import android.graphics.Bitmap; 22import android.util.Log; 23import android.view.SurfaceHolder; 24 25/** 26 * This class represents a video clip item on the storyboard 27 * {@hide} 28 */ 29public class MediaVideoItem extends MediaItem { 30 // Logging 31 private static final String TAG = "MediaVideoItem"; 32 33 // Instance variables 34 private final int mWidth; 35 private final int mHeight; 36 private final int mAspectRatio; 37 private final int mFileType; 38 private final int mVideoType; 39 private final int mVideoProfile; 40 private final int mVideoBitrate; 41 private final long mDurationMs; 42 private final int mAudioBitrate; 43 private final int mFps; 44 private final int mAudioType; 45 private final int mAudioChannels; 46 private final int mAudioSamplingFrequency; 47 48 private long mBeginBoundaryTimeMs; 49 private long mEndBoundaryTimeMs; 50 private int mVolumePercentage; 51 private boolean mMuted; 52 private String mAudioWaveformFilename; 53 private PlaybackThread mPlaybackThread; 54 55 /** 56 * This listener interface is used by the MediaVideoItem to emit playback 57 * progress notifications. This callback should be invoked after the 58 * number of frames specified by 59 * {@link #startPlayback(SurfaceHolder surfaceHolder, long fromMs, 60 * int callbackAfterFrameCount, PlaybackProgressListener listener)} 61 */ 62 public interface PlaybackProgressListener { 63 /** 64 * This method notifies the listener of the current time position while 65 * playing a media item 66 * 67 * @param mediaItem The media item 68 * @param timeMs The current playback position (expressed in milliseconds 69 * since the beginning of the media item). 70 * @param end true if the end of the media item was reached 71 */ 72 public void onProgress(MediaVideoItem mediaItem, long timeMs, boolean end); 73 } 74 75 /** 76 * The playback thread 77 */ 78 private class PlaybackThread extends Thread { 79 // Instance variables 80 private final static long FRAME_DURATION = 33; 81 private final PlaybackProgressListener mListener; 82 private final int mCallbackAfterFrameCount; 83 private final long mFromMs, mToMs; 84 private boolean mRun; 85 private final boolean 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, 0, END_OF_FILE, 100, false, 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 beginMs Start time in milliseconds. Set to 0 to extract from the 219 * beginning 220 * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to 221 * extract until the end 222 * @param volumePercent in %/. 100% means no change; 50% means half value, 200% 223 * means double, 0% means silent. 224 * @param muted true if the audio is muted 225 * @param audioWaveformFilename The name of the audio waveform file 226 * 227 * @throws IOException if the file cannot be opened for reading 228 */ 229 MediaVideoItem(String mediaItemId, String filename, int renderingMode, 230 long beginMs, long endMs, int volumePercent, boolean muted, 231 String audioWaveformFilename) throws IOException { 232 super(mediaItemId, filename, renderingMode); 233 // TODO: Set these variables correctly 234 mWidth = 1080; 235 mHeight = 720; 236 mAspectRatio = MediaProperties.ASPECT_RATIO_3_2; 237 mFileType = MediaProperties.FILE_MP4; 238 mVideoType = MediaProperties.VCODEC_H264BP; 239 // Do we have predefined values for this variable? 240 mVideoProfile = 0; 241 // Can video and audio duration be different? 242 mDurationMs = 10000; 243 mVideoBitrate = 800000; 244 mAudioBitrate = 30000; 245 mFps = 30; 246 mAudioType = MediaProperties.ACODEC_AAC_LC; 247 mAudioChannels = 2; 248 mAudioSamplingFrequency = 16000; 249 250 mBeginBoundaryTimeMs = beginMs; 251 mEndBoundaryTimeMs = endMs == END_OF_FILE ? mDurationMs : endMs; 252 mVolumePercentage = volumePercent; 253 mMuted = muted; 254 mAudioWaveformFilename = audioWaveformFilename; 255 } 256 257 /** 258 * Sets the start and end marks for trimming a video media item. 259 * This method will adjust the duration of bounding transitions, effects 260 * and overlays if the current duration of the transactions become greater 261 * than the maximum allowable duration. 262 * 263 * @param beginMs Start time in milliseconds. Set to 0 to extract from the 264 * beginning 265 * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to 266 * extract until the end 267 * 268 * @throws IllegalArgumentException if the start time is greater or equal than 269 * end time, the end time is beyond the file duration, the start time 270 * is negative 271 */ 272 public void setExtractBoundaries(long beginMs, long endMs) { 273 if (beginMs > mDurationMs) { 274 throw new IllegalArgumentException("Invalid start time"); 275 } 276 if (endMs > mDurationMs) { 277 throw new IllegalArgumentException("Invalid end time"); 278 } 279 280 if (beginMs != mBeginBoundaryTimeMs) { 281 if (mBeginTransition != null) { 282 mBeginTransition.invalidate(); 283 } 284 } 285 286 if (endMs != mEndBoundaryTimeMs) { 287 if (mEndTransition != null) { 288 mEndTransition.invalidate(); 289 } 290 } 291 292 mBeginBoundaryTimeMs = beginMs; 293 mEndBoundaryTimeMs = endMs; 294 295 adjustElementsDuration(); 296 } 297 298 /** 299 * @return The boundary begin time 300 */ 301 public long getBoundaryBeginTime() { 302 return mBeginBoundaryTimeMs; 303 } 304 305 /** 306 * @return The boundary end time 307 */ 308 public long getBoundaryEndTime() { 309 return mEndBoundaryTimeMs; 310 } 311 312 /* 313 * {@inheritDoc} 314 */ 315 @Override 316 public void addEffect(Effect effect) { 317 if (effect instanceof EffectKenBurns) { 318 throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem"); 319 } 320 super.addEffect(effect); 321 } 322 323 /* 324 * {@inheritDoc} 325 */ 326 @Override 327 public Bitmap getThumbnail(int width, int height, long timeMs) { 328 return null; 329 } 330 331 /* 332 * {@inheritDoc} 333 */ 334 @Override 335 public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs, 336 int thumbnailCount) throws IOException { 337 return null; 338 } 339 340 /* 341 * {@inheritDoc} 342 */ 343 @Override 344 public int getAspectRatio() { 345 return mAspectRatio; 346 } 347 348 /* 349 * {@inheritDoc} 350 */ 351 @Override 352 public int getFileType() { 353 return mFileType; 354 } 355 356 /* 357 * {@inheritDoc} 358 */ 359 @Override 360 public int getWidth() { 361 return mWidth; 362 } 363 364 /* 365 * {@inheritDoc} 366 */ 367 @Override 368 public int getHeight() { 369 return mHeight; 370 } 371 372 /** 373 * @return The duration of the video clip 374 */ 375 public long getDuration() { 376 return mDurationMs; 377 } 378 379 /** 380 * @return The timeline duration. This is the actual duration in the 381 * timeline (trimmed duration) 382 */ 383 @Override 384 public long getTimelineDuration() { 385 return mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 386 } 387 388 /** 389 * Render a frame according to the playback (in the native aspect ratio) for 390 * the specified media item. All effects and overlays applied to the media 391 * item are ignored. The extract boundaries are also ignored. This method 392 * can be used to playback frames when implementing trimming functionality. 393 * 394 * @param surfaceHolder SurfaceHolder used by the application 395 * @param timeMs time corresponding to the frame to display (relative to the 396 * the beginning of the media item). 397 * @return The accurate time stamp of the frame that is rendered . 398 * @throws IllegalStateException if a playback, preview or an export is 399 * already in progress 400 * @throws IllegalArgumentException if time is negative or greater than the 401 * media item duration 402 */ 403 public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) { 404 return timeMs; 405 } 406 407 /** 408 * Start the playback of this media item. This method does not block (does 409 * not wait for the playback to complete). The PlaybackProgressListener 410 * allows to track the progress at the time interval determined by the 411 * callbackAfterFrameCount parameter. The SurfaceHolder has to be created 412 * and ready for use before calling this method. 413 * 414 * @param surfaceHolder SurfaceHolder where the frames are rendered. 415 * @param fromMs The time (relative to the beginning of the media item) at 416 * which the playback will start 417 * @param toMs The time (relative to the beginning of the media item) at 418 * which the playback will stop. Use -1 to play to the end of the 419 * media item 420 * @param loop true if the playback should be looped once it reaches the end 421 * @param callbackAfterFrameCount The listener interface should be invoked 422 * after the number of frames specified by this parameter. 423 * @param listener The listener which will be notified of the playback 424 * progress 425 * @throws IllegalArgumentException if fromMs or toMs is beyond the playback 426 * duration 427 * @throws IllegalStateException if a playback, preview or an export is 428 * already in progress 429 */ 430 public void startPlayback(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop, 431 int callbackAfterFrameCount, PlaybackProgressListener listener) { 432 if (fromMs >= mDurationMs) { 433 return; 434 } 435 mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, callbackAfterFrameCount, 436 listener); 437 mPlaybackThread.start(); 438 } 439 440 /** 441 * Stop the media item playback. This method blocks until the ongoing 442 * playback is stopped. 443 * 444 * @return The accurate current time when stop is effective expressed in 445 * milliseconds 446 */ 447 public long stopPlayback() { 448 final long stopTimeMs; 449 if (mPlaybackThread != null) { 450 stopTimeMs = mPlaybackThread.stopPlayback(); 451 mPlaybackThread = null; 452 } else { 453 stopTimeMs = 0; 454 } 455 return stopTimeMs; 456 } 457 458 /** 459 * This API allows to generate a file containing the sample volume levels of 460 * the Audio track of this media item. This function may take significant 461 * time and is blocking. The file can be retrieved using 462 * getAudioWaveformFilename(). 463 * 464 * @param listener The progress listener 465 * 466 * @throws IOException if the output file cannot be created 467 * @throws IllegalArgumentException if the mediaItem does not have a valid 468 * Audio track 469 */ 470 public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 471 throws IOException { 472 // TODO: Set mAudioWaveformFilename at the end once the export is complete 473 } 474 475 /** 476 * Get the audio waveform file name if {@link #extractAudioWaveform()} was 477 * successful. The file format is as following: 478 * <ul> 479 * <li>first 4 bytes provide the number of samples for each value, as big-endian signed</li> 480 * <li>4 following bytes is the total number of values in the file, as big-endian signed</li> 481 * <li>all values follow as bytes Name is unique.</li> 482 *</ul> 483 * @return the name of the file, null if the file has not been computed or 484 * if there is no Audio track in the mediaItem 485 */ 486 public String getAudioWaveformFilename() { 487 return mAudioWaveformFilename; 488 } 489 490 /** 491 * Set volume of the Audio track of this mediaItem 492 * 493 * @param volumePercent in %/. 100% means no change; 50% means half value, 200% 494 * means double, 0% means silent. 495 * @throws UsupportedOperationException if volume value is not supported 496 */ 497 public void setVolume(int volumePercent) { 498 mVolumePercentage = volumePercent; 499 } 500 501 /** 502 * Get the volume value of the audio track as percentage. Call of this 503 * method before calling setVolume will always return 100% 504 * 505 * @return the volume in percentage 506 */ 507 public int getVolume() { 508 return mVolumePercentage; 509 } 510 511 /** 512 * @param muted true to mute the media item 513 */ 514 public void setMute(boolean muted) { 515 mMuted = muted; 516 } 517 518 /** 519 * @return true if the media item is muted 520 */ 521 public boolean isMuted() { 522 return mMuted; 523 } 524 525 /** 526 * @return The video type 527 */ 528 public int getVideoType() { 529 return mVideoType; 530 } 531 532 /** 533 * @return The video profile 534 */ 535 public int getVideoProfile() { 536 return mVideoProfile; 537 } 538 539 /** 540 * @return The video bitrate 541 */ 542 public int getVideoBitrate() { 543 return mVideoBitrate; 544 } 545 546 /** 547 * @return The audio bitrate 548 */ 549 public int getAudioBitrate() { 550 return mAudioBitrate; 551 } 552 553 /** 554 * @return The number of frames per second 555 */ 556 public int getFps() { 557 return mFps; 558 } 559 560 /** 561 * @return The audio codec 562 */ 563 public int getAudioType() { 564 return mAudioType; 565 } 566 567 /** 568 * @return The number of audio channels 569 */ 570 public int getAudioChannels() { 571 return mAudioChannels; 572 } 573 574 /** 575 * @return The audio sample frequency 576 */ 577 public int getAudioSamplingFrequency() { 578 return mAudioSamplingFrequency; 579 } 580} 581