AudioTrack.java revision 0ca0a12c6b49032065bf64e9f1cdebf765a0df9d
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.util.Log; 22 23/** 24 * This class allows to handle an audio track. This audio file is mixed with the 25 * audio samples of the MediaItems. 26 * {@hide} 27 */ 28public class AudioTrack { 29 // Logging 30 private static final String TAG = "AudioTrack"; 31 32 // Instance variables 33 private final String mUniqueId; 34 private final String mFilename; 35 private long mStartTimeMs; 36 private long mTimelineDurationMs; 37 private int mVolumePercent; 38 private long mBeginBoundaryTimeMs; 39 private long mEndBoundaryTimeMs; 40 private boolean mLoop; 41 private boolean mMuted; 42 43 private final long mDurationMs; 44 private final int mAudioChannels; 45 private final int mAudioType; 46 private final int mAudioBitrate; 47 private final int mAudioSamplingFrequency; 48 49 // Ducking variables 50 private int mDuckingThreshold; 51 private int mDuckingLowVolume; 52 private boolean mIsDuckingEnabled; 53 54 // The audio waveform filename 55 private String mAudioWaveformFilename; 56 private PlaybackThread mPlaybackThread; 57 58 /** 59 * This listener interface is used by the AudioTrack to emit playback 60 * progress notifications. 61 */ 62 public interface PlaybackProgressListener { 63 /** 64 * This method notifies the listener of the current time position while 65 * playing an audio track 66 * 67 * @param audioTrack The audio track 68 * @param timeMs The current playback position (expressed in milliseconds 69 * since the beginning of the audio track). 70 * @param end true if the end of the audio track was reached 71 */ 72 public void onProgress(AudioTrack audioTrack, long timeMs, boolean end); 73 } 74 75 /** 76 * The playback thread 77 */ 78 private class PlaybackThread extends Thread { 79 // Instance variables 80 private final PlaybackProgressListener mListener; 81 private final long mFromMs, mToMs; 82 private boolean mRun; 83 private final boolean mLoop; 84 private long mPositionMs; 85 86 /** 87 * Constructor 88 * 89 * @param fromMs The time (relative to the beginning of the audio track) 90 * at which the playback will start 91 * @param toMs The time (relative to the beginning of the audio track) at 92 * which the playback will stop. Use -1 to play to the end of 93 * the audio track 94 * @param loop true if the playback should be looped once it reaches the 95 * end 96 * @param listener The listener which will be notified of the playback 97 * progress 98 */ 99 public PlaybackThread(long fromMs, long toMs, boolean loop, 100 PlaybackProgressListener listener) { 101 mPositionMs = mFromMs = fromMs; 102 if (toMs < 0) { 103 mToMs = mDurationMs; 104 } else { 105 mToMs = toMs; 106 } 107 mLoop = loop; 108 mListener = listener; 109 mRun = true; 110 } 111 112 /* 113 * {@inheritDoc} 114 */ 115 @Override 116 public void run() { 117 if (Log.isLoggable(TAG, Log.DEBUG)) { 118 Log.d(TAG, "===> PlaybackThread.run enter"); 119 } 120 121 while (mRun) { 122 try { 123 sleep(100); 124 } catch (InterruptedException ex) { 125 break; 126 } 127 128 mPositionMs += 100; 129 130 if (mPositionMs >= mToMs) { 131 if (!mLoop) { 132 if (mListener != null) { 133 mListener.onProgress(AudioTrack.this, mPositionMs, true); 134 } 135 if (Log.isLoggable(TAG, Log.DEBUG)) { 136 Log.d(TAG, "PlaybackThread.run playback complete"); 137 } 138 break; 139 } else { 140 // Fire a notification for the end of the clip 141 if (mListener != null) { 142 mListener.onProgress(AudioTrack.this, mToMs, false); 143 } 144 145 // Rewind 146 mPositionMs = mFromMs; 147 if (mListener != null) { 148 mListener.onProgress(AudioTrack.this, mPositionMs, false); 149 } 150 if (Log.isLoggable(TAG, Log.DEBUG)) { 151 Log.d(TAG, "PlaybackThread.run playback complete"); 152 } 153 } 154 } else { 155 if (mListener != null) { 156 mListener.onProgress(AudioTrack.this, mPositionMs, false); 157 } 158 } 159 } 160 if (Log.isLoggable(TAG, Log.DEBUG)) { 161 Log.d(TAG, "===> PlaybackThread.run exit"); 162 } 163 } 164 165 /** 166 * Stop the playback 167 * 168 * @return The stop position 169 */ 170 public long stopPlayback() { 171 mRun = false; 172 try { 173 join(); 174 } catch (InterruptedException ex) { 175 } 176 return mPositionMs; 177 } 178 }; 179 180 /** 181 * An object of this type cannot be instantiated by using the default 182 * constructor 183 */ 184 @SuppressWarnings("unused") 185 private AudioTrack() throws IOException { 186 this(null, null, null); 187 } 188 189 /** 190 * Constructor 191 * 192 * @param editor The video editor reference 193 * @param audioTrackId The audio track id 194 * @param filename The absolute file name 195 * 196 * @throws IOException if file is not found 197 * @throws IllegalArgumentException if file format is not supported or if 198 * the codec is not supported 199 */ 200 public AudioTrack(VideoEditor editor, String audioTrackId, String filename) 201 throws IOException { 202 mUniqueId = audioTrackId; 203 mFilename = filename; 204 mStartTimeMs = 0; 205 // TODO: This value represents to the duration of the audio file 206 mDurationMs = 300000; 207 // TODO: This value needs to be read from the audio track of the source 208 // file 209 mAudioChannels = 2; 210 mAudioType = MediaProperties.ACODEC_AAC_LC; 211 mAudioBitrate = 128000; 212 mAudioSamplingFrequency = 44100; 213 214 mTimelineDurationMs = mDurationMs; 215 mVolumePercent = 100; 216 217 // Play the entire audio track 218 mBeginBoundaryTimeMs = 0; 219 mEndBoundaryTimeMs = mDurationMs; 220 221 // By default loop is disabled 222 mLoop = false; 223 224 // By default the audio track is not muted 225 mMuted = false; 226 227 // Ducking is enabled by default 228 mDuckingThreshold = 0; 229 mDuckingLowVolume = 0; 230 mIsDuckingEnabled = true; 231 232 // The audio waveform file is generated later 233 mAudioWaveformFilename = null; 234 } 235 236 /** 237 * Constructor 238 * 239 * @param editor The video editor reference 240 * @param audioTrackId The audio track id 241 * @param filename The audio filename 242 * @param startTimeMs the start time in milliseconds (relative to the 243 * timeline) 244 * @param beginMs start time in the audio track in milliseconds (relative to 245 * the beginning of the audio track) 246 * @param endMs end time in the audio track in milliseconds (relative to the 247 * beginning of the audio track) 248 * @param loop true to loop the audio track 249 * @param volume The volume in percentage 250 * @param muted true if the audio track is muted 251 * @param audioWaveformFilename The name of the waveform file 252 * 253 * @throws IOException if file is not found 254 */ 255 AudioTrack(VideoEditor editor, String audioTrackId, String filename, long startTimeMs, 256 long beginMs, long endMs, boolean loop, int volume, boolean muted, 257 String audioWaveformFilename) throws IOException { 258 mUniqueId = audioTrackId; 259 mFilename = filename; 260 mStartTimeMs = startTimeMs; 261 262 // TODO: This value represents to the duration of the audio file 263 mDurationMs = 300000; 264 265 // TODO: This value needs to be read from the audio track of the source 266 // file 267 mAudioChannels = 2; 268 mAudioType = MediaProperties.ACODEC_AAC_LC; 269 mAudioBitrate = 128000; 270 mAudioSamplingFrequency = 44100; 271 272 mTimelineDurationMs = endMs - beginMs; 273 mVolumePercent = volume; 274 275 mBeginBoundaryTimeMs = beginMs; 276 mEndBoundaryTimeMs = endMs; 277 278 mLoop = loop; 279 mMuted = muted; 280 281 mAudioWaveformFilename = audioWaveformFilename; 282 } 283 284 /** 285 * @return The id of the audio track 286 */ 287 public String getId() { 288 return mUniqueId; 289 } 290 291 /** 292 * Get the filename source for this audio track. 293 * 294 * @return The filename as an absolute file name 295 */ 296 public String getFilename() { 297 return mFilename; 298 } 299 300 /** 301 * @return The number of audio channels in the source of this audio track 302 */ 303 public int getAudioChannels() { 304 return mAudioChannels; 305 } 306 307 /** 308 * @return The audio codec of the source of this audio track 309 */ 310 public int getAudioType() { 311 return mAudioType; 312 } 313 314 /** 315 * @return The audio sample frequency of the audio track 316 */ 317 public int getAudioSamplingFrequency() { 318 return mAudioSamplingFrequency; 319 } 320 321 /** 322 * @return The audio bitrate of the audio track 323 */ 324 public int getAudioBitrate() { 325 return mAudioBitrate; 326 } 327 328 /** 329 * Set the volume of this audio track as percentage of the volume in the 330 * original audio source file. 331 * 332 * @param volumePercent Percentage of the volume to apply. If it is set to 333 * 0, then volume becomes mute. It it is set to 100, then volume 334 * is same as original volume. It it is set to 200, then volume 335 * is doubled (provided that volume amplification is supported) 336 * 337 * @throws UnsupportedOperationException if volume amplification is 338 * requested and is not supported. 339 */ 340 public void setVolume(int volumePercent) { 341 mVolumePercent = volumePercent; 342 } 343 344 /** 345 * Get the volume of the audio track as percentage of the volume in the 346 * original audio source file. 347 * 348 * @return The volume in percentage 349 */ 350 public int getVolume() { 351 return mVolumePercent; 352 } 353 354 /** 355 * @param muted true to mute the audio track 356 */ 357 public void setMute(boolean muted) { 358 mMuted = muted; 359 } 360 361 /** 362 * @return true if the audio track is muted 363 */ 364 public boolean isMuted() { 365 return mMuted; 366 } 367 368 /** 369 * Set the start time of this audio track relative to the storyboard 370 * timeline. Default value is 0. 371 * 372 * @param startTimeMs the start time in milliseconds 373 */ 374 public void setStartTime(long startTimeMs) { 375 mStartTimeMs = startTimeMs; 376 } 377 378 /** 379 * Get the start time of this audio track relative to the storyboard 380 * timeline. 381 * 382 * @return The start time in milliseconds 383 */ 384 public long getStartTime() { 385 return mStartTimeMs; 386 } 387 388 /** 389 * @return The duration in milliseconds. This value represents the audio 390 * track duration (not looped) 391 */ 392 public long getDuration() { 393 return mDurationMs; 394 } 395 396 /** 397 * @return The timeline duration. If looping is enabled this value 398 * represents the duration of the looped audio track, otherwise it 399 * is the duration of the audio track (mDurationMs). 400 */ 401 public long getTimelineDuration() { 402 return mTimelineDurationMs; 403 } 404 405 /** 406 * Sets the start and end marks for trimming an audio track 407 * 408 * @param beginMs start time in the audio track in milliseconds (relative to 409 * the beginning of the audio track) 410 * @param endMs end time in the audio track in milliseconds (relative to the 411 * beginning of the audio track) 412 */ 413 public void setExtractBoundaries(long beginMs, long endMs) { 414 if (beginMs > mDurationMs) { 415 throw new IllegalArgumentException("Invalid start time"); 416 } 417 if (endMs > mDurationMs) { 418 throw new IllegalArgumentException("Invalid end time"); 419 } 420 421 mBeginBoundaryTimeMs = beginMs; 422 mEndBoundaryTimeMs = endMs; 423 if (mLoop) { 424 // TODO: Compute mDurationMs (from the beginning of the loop until 425 // the end of all the loops. 426 mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 427 } else { 428 mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 429 } 430 } 431 432 /** 433 * @return The boundary begin time 434 */ 435 public long getBoundaryBeginTime() { 436 return mBeginBoundaryTimeMs; 437 } 438 439 /** 440 * @return The boundary end time 441 */ 442 public long getBoundaryEndTime() { 443 return mEndBoundaryTimeMs; 444 } 445 446 /** 447 * Enable the loop mode for this audio track. Note that only one of the 448 * audio tracks in the timeline can have the loop mode enabled. When looping 449 * is enabled the samples between mBeginBoundaryTimeMs and 450 * mEndBoundaryTimeMs are looped. 451 */ 452 public void enableLoop() { 453 mLoop = true; 454 } 455 456 /** 457 * Disable the loop mode 458 */ 459 public void disableLoop() { 460 mLoop = false; 461 } 462 463 /** 464 * @return true if looping is enabled 465 */ 466 public boolean isLooping() { 467 return mLoop; 468 } 469 470 /** 471 * Disable the audio duck effect 472 */ 473 public void disableDucking() { 474 mIsDuckingEnabled = false; 475 } 476 477 /** 478 * TODO DEFINE 479 * 480 * @param threshold 481 * @param lowVolume 482 * @param volume 483 */ 484 public void enableDucking(int threshold, int lowVolume, int volume) { 485 mDuckingThreshold = threshold; 486 mDuckingLowVolume = lowVolume; 487 mIsDuckingEnabled = true; 488 } 489 490 /** 491 * @return true if ducking is enabled 492 */ 493 public boolean isDuckingEnabled() { 494 return mIsDuckingEnabled; 495 } 496 497 /** 498 * @return The ducking threshold 499 */ 500 public int getDuckingThreshhold() { 501 return mDuckingThreshold; 502 } 503 504 /** 505 * @return The ducking low level 506 */ 507 public int getDuckingLowVolume() { 508 return mDuckingLowVolume; 509 } 510 511 /** 512 * Start the playback of this audio track. This method does not block (does 513 * not wait for the playback to complete). 514 * 515 * @param fromMs The time (relative to the beginning of the audio track) at 516 * which the playback will start 517 * @param toMs The time (relative to the beginning of the audio track) at 518 * which the playback will stop. Use -1 to play to the end of the 519 * audio track 520 * @param loop true if the playback should be looped once it reaches the end 521 * @param listener The listener which will be notified of the playback 522 * progress 523 * @throws IllegalArgumentException if fromMs or toMs is beyond the playback 524 * duration 525 * @throws IllegalStateException if a playback, preview or an export is 526 * already in progress 527 */ 528 public void startPlayback(long fromMs, long toMs, boolean loop, 529 PlaybackProgressListener listener) { 530 if (fromMs >= mDurationMs) { 531 return; 532 } 533 mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, listener); 534 mPlaybackThread.start(); 535 } 536 537 /** 538 * Stop the audio track playback. This method blocks until the ongoing 539 * playback is stopped. 540 * 541 * @return The accurate current time when stop is effective expressed in 542 * milliseconds 543 */ 544 public long stopPlayback() { 545 final long stopTimeMs; 546 if (mPlaybackThread != null) { 547 stopTimeMs = mPlaybackThread.stopPlayback(); 548 mPlaybackThread = null; 549 } else { 550 stopTimeMs = 0; 551 } 552 return stopTimeMs; 553 } 554 555 /** 556 * This API allows to generate a file containing the sample volume levels of 557 * this audio track object. This function may take significant time and is 558 * blocking. The filename can be retrieved using getAudioWaveformFilename(). 559 * 560 * @param listener The progress listener 561 * 562 * @throws IOException if the output file cannot be created 563 * @throws IllegalArgumentException if the audio file does not have a valid 564 * audio track 565 */ 566 public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 567 throws IOException { 568 // TODO: Set mAudioWaveformFilename at the end once the extract is 569 // complete 570 } 571 572 /** 573 * Get the audio waveform file name if extractAudioWaveform was successful. 574 * The file format is as following: 575 * <ul> 576 * <li>first 4 bytes provide the number of samples for each value, as 577 * big-endian signed</li> 578 * <li>4 following bytes is the total number of values in the file, as 579 * big-endian signed</li> 580 * <li>then, all values follow as bytes</li> 581 * </ul> 582 * 583 * @return the name of the file, null if the file does not exist 584 */ 585 public String getAudioWaveformFilename() { 586 return mAudioWaveformFilename; 587 } 588 589 /* 590 * {@inheritDoc} 591 */ 592 @Override 593 public boolean equals(Object object) { 594 if (!(object instanceof AudioTrack)) { 595 return false; 596 } 597 return mUniqueId.equals(((AudioTrack)object).mUniqueId); 598 } 599 600 /* 601 * {@inheritDoc} 602 */ 603 @Override 604 public int hashCode() { 605 return mUniqueId.hashCode(); 606 } 607} 608