AudioTrack.java revision 9bcedf7cf3e9c981837f2d8ec98cd118efad3f01
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 17 18package android.media.videoeditor; 19 20import java.io.File; 21import java.io.IOException; 22import java.lang.ref.SoftReference; 23 24import android.media.videoeditor.MediaArtistNativeHelper.Properties; 25 26/** 27 * This class allows to handle an audio track. This audio file is mixed with the 28 * audio samples of the media items. 29 * {@hide} 30 */ 31public class AudioTrack { 32 33 /** 34 * Instance variables 35 * Private object for calling native methods via MediaArtistNativeHelper 36 */ 37 private final MediaArtistNativeHelper mMANativeHelper; 38 private final String mUniqueId; 39 private final String mFilename; 40 private long mStartTimeMs; 41 private long mTimelineDurationMs; 42 private int mVolumePercent; 43 private long mBeginBoundaryTimeMs; 44 private long mEndBoundaryTimeMs; 45 private boolean mLoop; 46 private boolean mMuted; 47 private final long mDurationMs; 48 private final int mAudioChannels; 49 private final int mAudioType; 50 private final int mAudioBitrate; 51 private final int mAudioSamplingFrequency; 52 53 /** 54 * Ducking variables 55 */ 56 private int mDuckingThreshold; 57 private int mDuckedTrackVolume; 58 private boolean mIsDuckingEnabled; 59 60 /** 61 * The audio waveform filename 62 */ 63 private String mAudioWaveformFilename; 64 65 /** 66 * The audio waveform data 67 */ 68 private SoftReference<WaveformData> mWaveformData; 69 70 /** 71 * An object of this type cannot be instantiated by using the default 72 * constructor 73 */ 74 @SuppressWarnings("unused") 75 private AudioTrack() throws IOException { 76 this(null, null, null); 77 } 78 79 /** 80 * Constructor 81 * 82 * @param editor The video editor reference 83 * @param audioTrackId The audio track id 84 * @param filename The absolute file name 85 * 86 * @throws IOException if file is not found 87 * @throws IllegalArgumentException if file format is not supported or if 88 * the codec is not supported or if editor is not of type 89 * VideoEditorImpl. 90 */ 91 public AudioTrack(VideoEditor editor, String audioTrackId, String filename) throws IOException { 92 this(editor, audioTrackId, filename, 0, 0, MediaItem.END_OF_FILE, false, 100, false, false, 93 0, 0, null); 94 } 95 96 /** 97 * Constructor 98 * 99 * @param editor The video editor reference 100 * @param audioTrackId The audio track id 101 * @param filename The audio filename. In case file contains Audio and Video, 102 * only the Audio stream will be used as Audio Track. 103 * @param startTimeMs the start time in milliseconds (relative to the 104 * timeline) 105 * @param beginMs start time in the audio track in milliseconds (relative to 106 * the beginning of the audio track) 107 * @param endMs end time in the audio track in milliseconds (relative to the 108 * beginning of the audio track) 109 * @param loop true to loop the audio track 110 * @param volume The volume in percentage 111 * @param muted true if the audio track is muted 112 * @param threshold Ducking will be activated when the relative energy in 113 * the media items audio signal goes above this value. The valid 114 * range of values is 0 to 90. 115 * @param duckedTrackVolume The relative volume of the audio track when 116 * ducking is active. The valid range of values is 0 to 100. 117 * @param audioWaveformFilename The name of the waveform file 118 * 119 * @throws IOException if file is not found 120 * @throws IllegalArgumentException if file format is not supported or if 121 * the codec is not supported or if editor is not of type 122 * VideoEditorImpl. 123 */ 124 AudioTrack(VideoEditor editor, String audioTrackId, String filename, 125 long startTimeMs,long beginMs, long endMs, boolean loop, 126 int volume, boolean muted,boolean duckingEnabled, 127 int duckThreshold, int duckedTrackVolume, 128 String audioWaveformFilename) throws IOException { 129 Properties properties = null; 130 File file = new File(filename); 131 if (!file.exists()) { 132 throw new IOException(filename + " not found ! "); 133 } 134 135 if (editor instanceof VideoEditorImpl) { 136 mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext(); 137 } else { 138 throw new IllegalArgumentException("editor is not of type VideoEditorImpl"); 139 } 140 try { 141 properties = mMANativeHelper.getMediaProperties(filename); 142 } catch (Exception e) { 143 throw new IllegalArgumentException("Unsupported file or file not found"); 144 } 145 switch (mMANativeHelper.getFileType(properties.fileType)) { 146 case MediaProperties.FILE_3GP: 147 case MediaProperties.FILE_MP4: 148 case MediaProperties.FILE_MP3: 149 break; 150 151 default: { 152 throw new IllegalArgumentException("Unsupported input file type"); 153 } 154 } 155 switch (mMANativeHelper.getAudioCodecType(properties.audioFormat)) { 156 case MediaProperties.ACODEC_AMRNB: 157 case MediaProperties.ACODEC_AMRWB: 158 case MediaProperties.ACODEC_AAC_LC: 159 case MediaProperties.ACODEC_MP3: 160 break; 161 default: 162 throw new IllegalArgumentException("Unsupported Audio Codec Format in Input File"); 163 } 164 165 if (endMs == MediaItem.END_OF_FILE) { 166 endMs = properties.audioDuration; 167 } 168 169 mUniqueId = audioTrackId; 170 mFilename = filename; 171 mStartTimeMs = startTimeMs; 172 mDurationMs = properties.audioDuration; 173 mAudioChannels = properties.audioChannels; 174 mAudioBitrate = properties.audioBitrate; 175 mAudioSamplingFrequency = properties.audioSamplingFrequency; 176 mAudioType = properties.audioFormat; 177 mTimelineDurationMs = endMs - beginMs; 178 mVolumePercent = volume; 179 180 mBeginBoundaryTimeMs = beginMs; 181 mEndBoundaryTimeMs = endMs; 182 183 mLoop = loop; 184 mMuted = muted; 185 mIsDuckingEnabled = duckingEnabled; 186 mDuckingThreshold = duckThreshold; 187 mDuckedTrackVolume = duckedTrackVolume; 188 189 mAudioWaveformFilename = audioWaveformFilename; 190 if (audioWaveformFilename != null) { 191 mWaveformData = 192 new SoftReference<WaveformData>(new WaveformData(audioWaveformFilename)); 193 } else { 194 mWaveformData = null; 195 } 196 } 197 198 /** 199 * Get the id of the audio track 200 * 201 * @return The id of the audio track 202 */ 203 public String getId() { 204 return mUniqueId; 205 } 206 207 /** 208 * Get the filename for this audio track source. 209 * 210 * @return The filename as an absolute file name 211 */ 212 public String getFilename() { 213 return mFilename; 214 } 215 216 /** 217 * Get the number of audio channels in the source of this audio track 218 * 219 * @return The number of audio channels in the source of this audio track 220 */ 221 public int getAudioChannels() { 222 return mAudioChannels; 223 } 224 225 /** 226 * Get the audio codec of the source of this audio track 227 * 228 * @return The audio codec of the source of this audio track 229 * {@link android.media.videoeditor.MediaProperties} 230 */ 231 public int getAudioType() { 232 return mAudioType; 233 } 234 235 /** 236 * Get the audio sample frequency of the audio track 237 * 238 * @return The audio sample frequency of the audio track 239 */ 240 public int getAudioSamplingFrequency() { 241 return mAudioSamplingFrequency; 242 } 243 244 /** 245 * Get the audio bitrate of the audio track 246 * 247 * @return The audio bitrate of the audio track 248 */ 249 public int getAudioBitrate() { 250 return mAudioBitrate; 251 } 252 253 /** 254 * Set the volume of this audio track as percentage of the volume in the 255 * original audio source file. 256 * 257 * @param volumePercent Percentage of the volume to apply. If it is set to 258 * 0, then volume becomes mute. It it is set to 100, then volume 259 * is same as original volume. It it is set to 200, then volume 260 * is doubled (provided that volume amplification is supported) 261 * 262 * @throws UnsupportedOperationException if volume amplification is 263 * requested and is not supported. 264 */ 265 public void setVolume(int volumePercent) { 266 if (volumePercent > MediaProperties.AUDIO_MAX_VOLUME_PERCENT) { 267 throw new IllegalArgumentException("Volume set exceeds maximum allowed value"); 268 } 269 270 if (volumePercent < 0) { 271 throw new IllegalArgumentException("Invalid Volume "); 272 } 273 mVolumePercent = volumePercent; 274 /** 275 * Force update of preview settings 276 */ 277 mMANativeHelper.setGeneratePreview(true); 278 } 279 280 /** 281 * Get the volume of the audio track as percentage of the volume in the 282 * original audio source file. 283 * 284 * @return The volume in percentage 285 */ 286 public int getVolume() { 287 return mVolumePercent; 288 } 289 290 /** 291 * Mute/Unmute the audio track 292 * 293 * @param muted true to mute the audio track. SetMute(true) will make 294 * the volume of this Audio Track to 0. 295 */ 296 public void setMute(boolean muted) { 297 mMuted = muted; 298 /** 299 * Force update of preview settings 300 */ 301 mMANativeHelper.setGeneratePreview(true); 302 } 303 304 /** 305 * Check if the audio track is muted 306 * 307 * @return true if the audio track is muted 308 */ 309 public boolean isMuted() { 310 return mMuted; 311 } 312 313 /** 314 * Get the start time of this audio track relative to the storyboard 315 * timeline. 316 * 317 * @return The start time in milliseconds 318 */ 319 320 public long getStartTime() { 321 return mStartTimeMs; 322 } 323 324 /** 325 * Get the audio track duration 326 * 327 * @return The duration in milliseconds. This value represents actual audio 328 * track duration. This value is not effected by 'enableLoop' or 329 * 'setExtractBoundaries'. 330 */ 331 public long getDuration() { 332 return mDurationMs; 333 } 334 335 /** 336 * Get the audio track timeline duration 337 * 338 * @return The timeline duration as defined by the begin and end boundaries 339 */ 340 public long getTimelineDuration() { 341 return mTimelineDurationMs; 342 } 343 344 /** 345 * Sets the start and end marks for trimming an audio track 346 * 347 * @param beginMs start time in the audio track in milliseconds (relative to 348 * the beginning of the audio track) 349 * @param endMs end time in the audio track in milliseconds (relative to the 350 * beginning of the audio track) 351 */ 352 public void setExtractBoundaries(long beginMs, long endMs) { 353 if (beginMs > mDurationMs) { 354 throw new IllegalArgumentException("Invalid start time"); 355 } 356 if (endMs > mDurationMs) { 357 throw new IllegalArgumentException("Invalid end time"); 358 } 359 if (beginMs < 0) { 360 throw new IllegalArgumentException("Invalid start time; is < 0"); 361 } 362 if (endMs < 0) { 363 throw new IllegalArgumentException("Invalid end time; is < 0"); 364 } 365 366 mBeginBoundaryTimeMs = beginMs; 367 mEndBoundaryTimeMs = endMs; 368 369 mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 370 /** 371 * Force update of preview settings 372 */ 373 mMANativeHelper.setGeneratePreview(true); 374 } 375 376 /** 377 * Get the boundary begin time 378 * 379 * @return The boundary begin time 380 */ 381 public long getBoundaryBeginTime() { 382 return mBeginBoundaryTimeMs; 383 } 384 385 /** 386 * Get the boundary end time 387 * 388 * @return The boundary end time 389 */ 390 public long getBoundaryEndTime() { 391 return mEndBoundaryTimeMs; 392 } 393 394 /** 395 * Enable the loop mode for this audio track. Note that only one of the 396 * audio tracks in the timeline can have the loop mode enabled. When looping 397 * is enabled the samples between mBeginBoundaryTimeMs and 398 * mEndBoundaryTimeMs are looped. 399 */ 400 public void enableLoop() { 401 if (!mLoop) { 402 mLoop = true; 403 /** 404 * Force update of preview settings 405 */ 406 mMANativeHelper.setGeneratePreview(true); 407 } 408 } 409 410 /** 411 * Disable the loop mode 412 */ 413 public void disableLoop() { 414 if (mLoop) { 415 mLoop = false; 416 /** 417 * Force update of preview settings 418 */ 419 mMANativeHelper.setGeneratePreview(true); 420 } 421 } 422 423 /** 424 * Check if looping is enabled 425 * 426 * @return true if looping is enabled 427 */ 428 public boolean isLooping() { 429 return mLoop; 430 } 431 432 /** 433 * Disable the audio duck effect 434 */ 435 public void disableDucking() { 436 if (mIsDuckingEnabled) { 437 mIsDuckingEnabled = false; 438 /** 439 * Force update of preview settings 440 */ 441 mMANativeHelper.setGeneratePreview(true); 442 } 443 } 444 445 /** 446 * Enable ducking by specifying the required parameters 447 * 448 * @param threshold Ducking will be activated when the energy in 449 * the media items audio signal goes above this value. The valid 450 * range of values is 0db to 90dB. 0dB is equivalent to disabling 451 * ducking. 452 * @param duckedTrackVolume The relative volume of the audio track when ducking 453 * is active. The valid range of values is 0 to 100. 454 */ 455 public void enableDucking(int threshold, int duckedTrackVolume) { 456 if (threshold < 0 || threshold > 90) { 457 throw new IllegalArgumentException("Invalid threshold value: " + threshold); 458 } 459 460 if (duckedTrackVolume < 0 || duckedTrackVolume > 100) { 461 throw new IllegalArgumentException("Invalid duckedTrackVolume value: " 462 + duckedTrackVolume); 463 } 464 465 mDuckingThreshold = threshold; 466 mDuckedTrackVolume = duckedTrackVolume; 467 mIsDuckingEnabled = true; 468 /** 469 * Force update of preview settings 470 */ 471 mMANativeHelper.setGeneratePreview(true); 472 } 473 474 /** 475 * Check if ducking is enabled 476 * 477 * @return true if ducking is enabled 478 */ 479 public boolean isDuckingEnabled() { 480 return mIsDuckingEnabled; 481 } 482 483 /** 484 * Get the ducking threshold. 485 * 486 * @return The ducking threshold 487 */ 488 public int getDuckingThreshhold() { 489 return mDuckingThreshold; 490 } 491 492 /** 493 * Get the ducked track volume. 494 * 495 * @return The ducked track volume 496 */ 497 public int getDuckedTrackVolume() { 498 return mDuckedTrackVolume; 499 } 500 501 /** 502 * This API allows to generate a file containing the sample volume levels of 503 * this audio track object. This function may take significant time and is 504 * blocking. The filename can be retrieved using getAudioWaveformFilename(). 505 * 506 * @param listener The progress listener 507 * 508 * @throws IOException if the output file cannot be created 509 * @throws IllegalArgumentException if the audio file does not have a valid 510 * audio track 511 * @throws IllegalStateException if the codec type is unsupported 512 */ 513 public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 514 throws IOException { 515 if (mAudioWaveformFilename == null) { 516 /** 517 * AudioWaveformFilename is generated 518 */ 519 final String projectPath = mMANativeHelper.getProjectPath(); 520 final String audioWaveFilename = String.format(projectPath + "/audioWaveformFile-" 521 + getId() + ".dat"); 522 523 /** 524 * Logic to get frame duration = (no. of frames per sample * 1000)/ 525 * sampling frequency 526 */ 527 final int frameDuration; 528 final int sampleCount; 529 final int codecType = mMANativeHelper.getAudioCodecType(mAudioType); 530 switch (codecType) { 531 case MediaProperties.ACODEC_AMRNB: { 532 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB * 1000) 533 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 534 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB; 535 break; 536 } 537 538 case MediaProperties.ACODEC_AMRWB: { 539 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000) 540 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 541 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB; 542 break; 543 } 544 545 case MediaProperties.ACODEC_AAC_LC: { 546 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000) 547 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 548 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC; 549 break; 550 } 551 552 case MediaProperties.ACODEC_MP3: { 553 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_MP3 * 1000) 554 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 555 sampleCount = MediaProperties.SAMPLES_PER_FRAME_MP3; 556 break; 557 } 558 559 default: { 560 throw new IllegalStateException("Unsupported codec type: " 561 + codecType); 562 } 563 } 564 565 mMANativeHelper.generateAudioGraph( mUniqueId, 566 mFilename, 567 audioWaveFilename, 568 frameDuration, 569 MediaProperties.DEFAULT_CHANNEL_COUNT, 570 sampleCount, 571 listener, 572 false); 573 /** 574 * Record the generated file name 575 */ 576 mAudioWaveformFilename = audioWaveFilename; 577 } 578 mWaveformData = new SoftReference<WaveformData>(new WaveformData(mAudioWaveformFilename)); 579 } 580 581 /** 582 * Get the audio waveform file name if extractAudioWaveform was successful. 583 * 584 * @return the name of the file, null if the file does not exist 585 */ 586 String getAudioWaveformFilename() { 587 return mAudioWaveformFilename; 588 } 589 590 /** 591 * Delete the waveform file 592 */ 593 void invalidate() { 594 if (mAudioWaveformFilename != null) { 595 new File(mAudioWaveformFilename).delete(); 596 mAudioWaveformFilename = null; 597 mWaveformData = null; 598 } 599 } 600 601 /** 602 * Get the audio waveform data. 603 * 604 * @return The waveform data 605 * 606 * @throws IOException if the waveform file cannot be found 607 */ 608 public WaveformData getWaveformData() throws IOException { 609 if (mWaveformData == null) { 610 return null; 611 } 612 613 WaveformData waveformData = mWaveformData.get(); 614 if (waveformData != null) { 615 return waveformData; 616 } else if (mAudioWaveformFilename != null) { 617 try { 618 waveformData = new WaveformData(mAudioWaveformFilename); 619 } catch (IOException e) { 620 throw e; 621 } 622 mWaveformData = new SoftReference<WaveformData>(waveformData); 623 return waveformData; 624 } else { 625 return null; 626 } 627 } 628 629 /* 630 * {@inheritDoc} 631 */ 632 @Override 633 public boolean equals(Object object) { 634 if (!(object instanceof AudioTrack)) { 635 return false; 636 } 637 return mUniqueId.equals(((AudioTrack)object).mUniqueId); 638 } 639 640 /* 641 * {@inheritDoc} 642 */ 643 @Override 644 public int hashCode() { 645 return mUniqueId.hashCode(); 646 } 647} 648