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