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