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