AudioTrack.java revision 2bb13573d619e3371d06847d36db78a20b17dfab
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 274 /** 275 * Force update of preview settings 276 */ 277 mMANativeHelper.setGeneratePreview(true); 278 279 mVolumePercent = volumePercent; 280 } 281 282 /** 283 * Get the volume of the audio track as percentage of the volume in the 284 * original audio source file. 285 * 286 * @return The volume in percentage 287 */ 288 public int getVolume() { 289 return mVolumePercent; 290 } 291 292 /** 293 * Mute/Unmute the audio track 294 * 295 * @param muted true to mute the audio track. SetMute(true) will make 296 * the volume of this Audio Track to 0. 297 */ 298 public void setMute(boolean muted) { 299 /** 300 * Force update of preview settings 301 */ 302 mMANativeHelper.setGeneratePreview(true); 303 mMuted = muted; 304 } 305 306 /** 307 * Check if the audio track is muted 308 * 309 * @return true if the audio track is muted 310 */ 311 public boolean isMuted() { 312 return mMuted; 313 } 314 315 /** 316 * Get the start time of this audio track relative to the storyboard 317 * timeline. 318 * 319 * @return The start time in milliseconds 320 */ 321 322 public long getStartTime() { 323 return mStartTimeMs; 324 } 325 326 /** 327 * Get the audio track duration 328 * 329 * @return The duration in milliseconds. This value represents actual audio 330 * track duration. This value is not effected by 'enableLoop' or 331 * 'setExtractBoundaries'. 332 */ 333 public long getDuration() { 334 return mDurationMs; 335 } 336 337 /** 338 * Get the audio track timeline duration 339 * 340 * @return The timeline duration as defined by the begin and end boundaries 341 */ 342 public long getTimelineDuration() { 343 return mTimelineDurationMs; 344 } 345 346 /** 347 * Sets the start and end marks for trimming an audio track 348 * 349 * @param beginMs start time in the audio track in milliseconds (relative to 350 * the beginning of the audio track) 351 * @param endMs end time in the audio track in milliseconds (relative to the 352 * beginning of the audio track) 353 */ 354 public void setExtractBoundaries(long beginMs, long endMs) { 355 if (beginMs > mDurationMs) { 356 throw new IllegalArgumentException("Invalid start time"); 357 } 358 if (endMs > mDurationMs) { 359 throw new IllegalArgumentException("Invalid end time"); 360 } 361 if (beginMs < 0) { 362 throw new IllegalArgumentException("Invalid start time; is < 0"); 363 } 364 if (endMs < 0) { 365 throw new IllegalArgumentException("Invalid end time; is < 0"); 366 } 367 368 /** 369 * Force update of preview settings 370 */ 371 mMANativeHelper.setGeneratePreview(true); 372 373 mBeginBoundaryTimeMs = beginMs; 374 mEndBoundaryTimeMs = endMs; 375 376 mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 377 } 378 379 /** 380 * Get the boundary begin time 381 * 382 * @return The boundary begin time 383 */ 384 public long getBoundaryBeginTime() { 385 return mBeginBoundaryTimeMs; 386 } 387 388 /** 389 * Get the boundary end time 390 * 391 * @return The boundary end time 392 */ 393 public long getBoundaryEndTime() { 394 return mEndBoundaryTimeMs; 395 } 396 397 /** 398 * Enable the loop mode for this audio track. Note that only one of the 399 * audio tracks in the timeline can have the loop mode enabled. When looping 400 * is enabled the samples between mBeginBoundaryTimeMs and 401 * mEndBoundaryTimeMs are looped. 402 */ 403 public void enableLoop() { 404 if (!mLoop) { 405 mLoop = true; 406 /** 407 * Force update of preview settings 408 */ 409 mMANativeHelper.setGeneratePreview(true); 410 } 411 } 412 413 /** 414 * Disable the loop mode 415 */ 416 public void disableLoop() { 417 if (mLoop) { 418 /** 419 * Force update of preview settings 420 */ 421 mMANativeHelper.setGeneratePreview(true); 422 mLoop = false; 423 } 424 } 425 426 /** 427 * Check if looping is enabled 428 * 429 * @return true if looping is enabled 430 */ 431 public boolean isLooping() { 432 return mLoop; 433 } 434 435 /** 436 * Disable the audio duck effect 437 */ 438 public void disableDucking() { 439 if (mIsDuckingEnabled) { 440 /** 441 * Force update of preview settings 442 */ 443 mMANativeHelper.setGeneratePreview(true); 444 mIsDuckingEnabled = false; 445 } 446 } 447 448 /** 449 * Enable ducking by specifying the required parameters 450 * 451 * @param threshold Ducking will be activated when the energy in 452 * the media items audio signal goes above this value. The valid 453 * range of values is 0db to 90dB. 0dB is equivalent to disabling 454 * ducking. 455 * @param duckedTrackVolume The relative volume of the audio track when ducking 456 * is active. The valid range of values is 0 to 100. 457 */ 458 public void enableDucking(int threshold, int duckedTrackVolume) { 459 if (threshold < 0 || threshold > 90) { 460 throw new IllegalArgumentException("Invalid threshold value: " + threshold); 461 } 462 463 if (duckedTrackVolume < 0 || duckedTrackVolume > 100) { 464 throw new IllegalArgumentException("Invalid duckedTrackVolume value: " 465 + duckedTrackVolume); 466 } 467 468 /** 469 * Force update of preview settings 470 */ 471 mMANativeHelper.setGeneratePreview(true); 472 473 mDuckingThreshold = threshold; 474 mDuckedTrackVolume = duckedTrackVolume; 475 mIsDuckingEnabled = true; 476 } 477 478 /** 479 * Check if ducking is enabled 480 * 481 * @return true if ducking is enabled 482 */ 483 public boolean isDuckingEnabled() { 484 return mIsDuckingEnabled; 485 } 486 487 /** 488 * Get the ducking threshold. 489 * 490 * @return The ducking threshold 491 */ 492 public int getDuckingThreshhold() { 493 return mDuckingThreshold; 494 } 495 496 /** 497 * Get the ducked track volume. 498 * 499 * @return The ducked track volume 500 */ 501 public int getDuckedTrackVolume() { 502 return mDuckedTrackVolume; 503 } 504 505 /** 506 * This API allows to generate a file containing the sample volume levels of 507 * this audio track object. This function may take significant time and is 508 * blocking. The filename can be retrieved using getAudioWaveformFilename(). 509 * 510 * @param listener The progress listener 511 * 512 * @throws IOException if the output file cannot be created 513 * @throws IllegalArgumentException if the audio file does not have a valid 514 * audio track 515 * @throws IllegalStateException if the codec type is unsupported 516 */ 517 public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 518 throws IOException { 519 if (mAudioWaveformFilename == null) { 520 /** 521 * AudioWaveformFilename is generated 522 */ 523 final String projectPath = mMANativeHelper.getProjectPath(); 524 final String audioWaveFilename = String.format(projectPath + "/audioWaveformFile-" 525 + getId() + ".dat"); 526 527 /** 528 * Logic to get frame duration = (no. of frames per sample * 1000)/ 529 * sampling frequency 530 */ 531 final int frameDuration; 532 final int sampleCount; 533 final int codecType = mMANativeHelper.getAudioCodecType(mAudioType); 534 switch (codecType) { 535 case MediaProperties.ACODEC_AMRNB: { 536 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB * 1000) 537 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 538 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB; 539 break; 540 } 541 542 case MediaProperties.ACODEC_AMRWB: { 543 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000) 544 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 545 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB; 546 break; 547 } 548 549 case MediaProperties.ACODEC_AAC_LC: { 550 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000) 551 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 552 sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC; 553 break; 554 } 555 556 case MediaProperties.ACODEC_MP3: { 557 frameDuration = (MediaProperties.SAMPLES_PER_FRAME_MP3 * 1000) 558 / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 559 sampleCount = MediaProperties.SAMPLES_PER_FRAME_MP3; 560 break; 561 } 562 563 default: { 564 throw new IllegalStateException("Unsupported codec type: " 565 + codecType); 566 } 567 } 568 569 mMANativeHelper.generateAudioGraph( mUniqueId, 570 mFilename, 571 audioWaveFilename, 572 frameDuration, 573 MediaProperties.DEFAULT_CHANNEL_COUNT, 574 sampleCount, 575 listener, 576 false); 577 /** 578 * Record the generated file name 579 */ 580 mAudioWaveformFilename = audioWaveFilename; 581 } 582 mWaveformData = new SoftReference<WaveformData>(new WaveformData(mAudioWaveformFilename)); 583 } 584 585 /** 586 * Get the audio waveform file name if extractAudioWaveform was successful. 587 * 588 * @return the name of the file, null if the file does not exist 589 */ 590 String getAudioWaveformFilename() { 591 return mAudioWaveformFilename; 592 } 593 594 /** 595 * Delete the waveform file 596 */ 597 void invalidate() { 598 if (mAudioWaveformFilename != null) { 599 new File(mAudioWaveformFilename).delete(); 600 mAudioWaveformFilename = null; 601 mWaveformData = null; 602 } 603 } 604 605 /** 606 * Get the audio waveform data. 607 * 608 * @return The waveform data 609 * 610 * @throws IOException if the waveform file cannot be found 611 */ 612 public WaveformData getWaveformData() throws IOException { 613 if (mWaveformData == null) { 614 return null; 615 } 616 617 WaveformData waveformData = mWaveformData.get(); 618 if (waveformData != null) { 619 return waveformData; 620 } else if (mAudioWaveformFilename != null) { 621 try { 622 waveformData = new WaveformData(mAudioWaveformFilename); 623 } catch (IOException e) { 624 throw e; 625 } 626 mWaveformData = new SoftReference<WaveformData>(waveformData); 627 return waveformData; 628 } else { 629 return null; 630 } 631 } 632 633 /* 634 * {@inheritDoc} 635 */ 636 @Override 637 public boolean equals(Object object) { 638 if (!(object instanceof AudioTrack)) { 639 return false; 640 } 641 return mUniqueId.equals(((AudioTrack)object).mUniqueId); 642 } 643 644 /* 645 * {@inheritDoc} 646 */ 647 @Override 648 public int hashCode() { 649 return mUniqueId.hashCode(); 650 } 651} 652