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