AudioTrack.java revision 61f9184547a3b9ad4c06846b87f1b1cbd4787230
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); 187 } 188 189 /** 190 * Constructor 191 * @param audioTrackId The audio track id 192 * @param filename The absolute file name 193 * 194 * @throws IOException if file is not found 195 * @throws IllegalArgumentException if file format is not supported or if 196 * the codec is not supported 197 */ 198 public AudioTrack(String audioTrackId, String filename) throws IOException { 199 mUniqueId = audioTrackId; 200 mFilename = filename; 201 mStartTimeMs = 0; 202 // TODO: This value represents to the duration of the audio file 203 mDurationMs = 300000; 204 // TODO: This value needs to be read from the audio track of the source 205 // file 206 mAudioChannels = 2; 207 mAudioType = MediaProperties.ACODEC_AAC_LC; 208 mAudioBitrate = 128000; 209 mAudioSamplingFrequency = 44100; 210 211 mTimelineDurationMs = mDurationMs; 212 mVolumePercent = 100; 213 214 // Play the entire audio track 215 mBeginBoundaryTimeMs = 0; 216 mEndBoundaryTimeMs = mDurationMs; 217 218 // By default loop is disabled 219 mLoop = false; 220 221 // By default the audio track is not muted 222 mMuted = false; 223 224 // Ducking is enabled by default 225 mDuckingThreshold = 0; 226 mDuckingLowVolume = 0; 227 mIsDuckingEnabled = true; 228 229 // The audio waveform file is generated later 230 mAudioWaveformFilename = null; 231 } 232 233 /** 234 * Constructor 235 * 236 * @param audioTrackId The audio track id 237 * @param filename The audio filename 238 * @param startTimeMs the start time in milliseconds (relative to the 239 * timeline) 240 * @param beginMs start time in the audio track in milliseconds (relative to 241 * the beginning of the audio track) 242 * @param endMs end time in the audio track in milliseconds (relative to the 243 * beginning of the audio track) 244 * @param loop true to loop the audio track 245 * @param volume The volume in percentage 246 * @param muted true if the audio track is muted 247 * @param audioWaveformFilename The name of the waveform file 248 * 249 * @throws IOException if file is not found 250 */ 251 AudioTrack(String audioTrackId, String filename, long startTimeMs, long beginMs, long endMs, 252 boolean loop, int volume, boolean muted, String audioWaveformFilename) throws IOException { 253 mUniqueId = audioTrackId; 254 mFilename = filename; 255 mStartTimeMs = startTimeMs; 256 257 // TODO: This value represents to the duration of the audio file 258 mDurationMs = 300000; 259 260 // TODO: This value needs to be read from the audio track of the source 261 // file 262 mAudioChannels = 2; 263 mAudioType = MediaProperties.ACODEC_AAC_LC; 264 mAudioBitrate = 128000; 265 mAudioSamplingFrequency = 44100; 266 267 mTimelineDurationMs = endMs - beginMs; 268 mVolumePercent = volume; 269 270 mBeginBoundaryTimeMs = beginMs; 271 mEndBoundaryTimeMs = endMs; 272 273 mLoop = loop; 274 mMuted = muted; 275 276 mAudioWaveformFilename = audioWaveformFilename; 277 } 278 279 /** 280 * @return The id of the audio track 281 */ 282 public String getId() { 283 return mUniqueId; 284 } 285 286 /** 287 * Get the filename source for this audio track. 288 * 289 * @return The filename as an absolute file name 290 */ 291 public String getFilename() { 292 return mFilename; 293 } 294 295 /** 296 * @return The number of audio channels in the source of this audio track 297 */ 298 public int getAudioChannels() { 299 return mAudioChannels; 300 } 301 302 /** 303 * @return The audio codec of the source of this audio track 304 */ 305 public int getAudioType() { 306 return mAudioType; 307 } 308 309 /** 310 * @return The audio sample frequency of the audio track 311 */ 312 public int getAudioSamplingFrequency() { 313 return mAudioSamplingFrequency; 314 } 315 316 /** 317 * @return The audio bitrate of the audio track 318 */ 319 public int getAudioBitrate() { 320 return mAudioBitrate; 321 } 322 323 /** 324 * Set the volume of this audio track as percentage of the volume in the 325 * original audio source file. 326 * 327 * @param volumePercent Percentage of the volume to apply. If it is set to 328 * 0, then volume becomes mute. It it is set to 100, then volume 329 * is same as original volume. It it is set to 200, then volume 330 * is doubled (provided that volume amplification is supported) 331 * 332 * @throws UnsupportedOperationException if volume amplification is 333 * requested and is not supported. 334 */ 335 public void setVolume(int volumePercent) { 336 mVolumePercent = volumePercent; 337 } 338 339 /** 340 * Get the volume of the audio track as percentage of the volume in the 341 * original audio source file. 342 * 343 * @return The volume in percentage 344 */ 345 public int getVolume() { 346 return mVolumePercent; 347 } 348 349 /** 350 * @param muted true to mute the audio track 351 */ 352 public void setMute(boolean muted) { 353 mMuted = muted; 354 } 355 356 /** 357 * @return true if the audio track is muted 358 */ 359 public boolean isMuted() { 360 return mMuted; 361 } 362 363 /** 364 * Set the start time of this audio track relative to the storyboard 365 * timeline. Default value is 0. 366 * 367 * @param startTimeMs the start time in milliseconds 368 */ 369 public void setStartTime(long startTimeMs) { 370 mStartTimeMs = startTimeMs; 371 } 372 373 /** 374 * Get the start time of this audio track relative to the storyboard 375 * timeline. 376 * 377 * @return The start time in milliseconds 378 */ 379 public long getStartTime() { 380 return mStartTimeMs; 381 } 382 383 /** 384 * @return The duration in milliseconds. This value represents the audio 385 * track duration (not looped) 386 */ 387 public long getDuration() { 388 return mDurationMs; 389 } 390 391 /** 392 * @return The timeline duration. If looping is enabled this value 393 * represents the duration of the looped audio track, otherwise it 394 * is the duration of the audio track (mDurationMs). 395 */ 396 public long getTimelineDuration() { 397 return mTimelineDurationMs; 398 } 399 400 /** 401 * Sets the start and end marks for trimming an audio track 402 * 403 * @param beginMs start time in the audio track in milliseconds (relative to 404 * the beginning of the audio track) 405 * @param endMs end time in the audio track in milliseconds (relative to the 406 * beginning of the audio track) 407 */ 408 public void setExtractBoundaries(long beginMs, long endMs) { 409 if (beginMs > mDurationMs) { 410 throw new IllegalArgumentException("Invalid start time"); 411 } 412 if (endMs > mDurationMs) { 413 throw new IllegalArgumentException("Invalid end time"); 414 } 415 416 mBeginBoundaryTimeMs = beginMs; 417 mEndBoundaryTimeMs = endMs; 418 if (mLoop) { 419 // TODO: Compute mDurationMs (from the beginning of the loop until 420 // the end of all the loops. 421 mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 422 } else { 423 mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 424 } 425 } 426 427 /** 428 * @return The boundary begin time 429 */ 430 public long getBoundaryBeginTime() { 431 return mBeginBoundaryTimeMs; 432 } 433 434 /** 435 * @return The boundary end time 436 */ 437 public long getBoundaryEndTime() { 438 return mEndBoundaryTimeMs; 439 } 440 441 /** 442 * Enable the loop mode for this audio track. Note that only one of the 443 * audio tracks in the timeline can have the loop mode enabled. When looping 444 * is enabled the samples between mBeginBoundaryTimeMs and 445 * mEndBoundaryTimeMs are looped. 446 */ 447 public void enableLoop() { 448 mLoop = true; 449 } 450 451 /** 452 * Disable the loop mode 453 */ 454 public void disableLoop() { 455 mLoop = false; 456 } 457 458 /** 459 * @return true if looping is enabled 460 */ 461 public boolean isLooping() { 462 return mLoop; 463 } 464 465 /** 466 * Disable the audio duck effect 467 */ 468 public void disableDucking() { 469 mIsDuckingEnabled = false; 470 } 471 472 /** 473 * TODO DEFINE 474 * 475 * @param threshold 476 * @param lowVolume 477 * @param volume 478 */ 479 public void enableDucking(int threshold, int lowVolume, int volume) { 480 mDuckingThreshold = threshold; 481 mDuckingLowVolume = lowVolume; 482 mIsDuckingEnabled = true; 483 } 484 485 /** 486 * @return true if ducking is enabled 487 */ 488 public boolean isDuckingEnabled() { 489 return mIsDuckingEnabled; 490 } 491 492 /** 493 * @return The ducking threshold 494 */ 495 public int getDuckingThreshhold() { 496 return mDuckingThreshold; 497 } 498 499 /** 500 * @return The ducking low level 501 */ 502 public int getDuckingLowVolume() { 503 return mDuckingLowVolume; 504 } 505 506 /** 507 * Start the playback of this audio track. This method does not block (does 508 * not wait for the playback to complete). 509 * 510 * @param fromMs The time (relative to the beginning of the audio track) at 511 * which the playback will start 512 * @param toMs The time (relative to the beginning of the audio track) at 513 * which the playback will stop. Use -1 to play to the end of the 514 * audio track 515 * @param loop true if the playback should be looped once it reaches the end 516 * @param listener The listener which will be notified of the playback 517 * progress 518 * @throws IllegalArgumentException if fromMs or toMs is beyond the playback 519 * duration 520 * @throws IllegalStateException if a playback, preview or an export is 521 * already in progress 522 */ 523 public void startPlayback(long fromMs, long toMs, boolean loop, 524 PlaybackProgressListener listener) { 525 if (fromMs >= mDurationMs) { 526 return; 527 } 528 mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, listener); 529 mPlaybackThread.start(); 530 } 531 532 /** 533 * Stop the audio track playback. This method blocks until the ongoing 534 * playback is stopped. 535 * 536 * @return The accurate current time when stop is effective expressed in 537 * milliseconds 538 */ 539 public long stopPlayback() { 540 final long stopTimeMs; 541 if (mPlaybackThread != null) { 542 stopTimeMs = mPlaybackThread.stopPlayback(); 543 mPlaybackThread = null; 544 } else { 545 stopTimeMs = 0; 546 } 547 return stopTimeMs; 548 } 549 550 /** 551 * This API allows to generate a file containing the sample volume levels of 552 * this audio track object. This function may take significant time and is 553 * blocking. The filename can be retrieved using getAudioWaveformFilename(). 554 * 555 * @param listener The progress listener 556 * 557 * @throws IOException if the output file cannot be created 558 * @throws IllegalArgumentException if the audio file does not have a valid 559 * audio track 560 */ 561 public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 562 throws IOException { 563 // TODO: Set mAudioWaveformFilename at the end once the extract is 564 // complete 565 } 566 567 /** 568 * Get the audio waveform file name if extractAudioWaveform was successful. 569 * The file format is as following: 570 * <ul> 571 * <li>first 4 bytes provide the number of samples for each value, as 572 * big-endian signed</li> 573 * <li>4 following bytes is the total number of values in the file, as 574 * big-endian signed</li> 575 * <li>then, all values follow as bytes</li> 576 * </ul> 577 * 578 * @return the name of the file, null if the file does not exist 579 */ 580 public String getAudioWaveformFilename() { 581 return mAudioWaveformFilename; 582 } 583 584 /* 585 * {@inheritDoc} 586 */ 587 @Override 588 public boolean equals(Object object) { 589 if (!(object instanceof AudioTrack)) { 590 return false; 591 } 592 return mUniqueId.equals(((AudioTrack)object).mUniqueId); 593 } 594 595 /* 596 * {@inheritDoc} 597 */ 598 @Override 599 public int hashCode() { 600 return mUniqueId.hashCode(); 601 } 602} 603