| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package android.media.videoeditor; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.ref.SoftReference; |
| |
| import android.media.videoeditor.MediaArtistNativeHelper.Properties; |
| |
| /** |
| * This class allows to handle an audio track. This audio file is mixed with the |
| * audio samples of the media items. |
| * {@hide} |
| */ |
| public class AudioTrack { |
| |
| /** |
| * Instance variables |
| * Private object for calling native methods via MediaArtistNativeHelper |
| */ |
| private final MediaArtistNativeHelper mMANativeHelper; |
| private final String mUniqueId; |
| private final String mFilename; |
| private long mStartTimeMs; |
| private long mTimelineDurationMs; |
| private int mVolumePercent; |
| private long mBeginBoundaryTimeMs; |
| private long mEndBoundaryTimeMs; |
| private boolean mLoop; |
| private boolean mMuted; |
| private final long mDurationMs; |
| private final int mAudioChannels; |
| private final int mAudioType; |
| private final int mAudioBitrate; |
| private final int mAudioSamplingFrequency; |
| /** |
| * Ducking variables |
| */ |
| private int mDuckingThreshold; |
| private int mDuckedTrackVolume; |
| private boolean mIsDuckingEnabled; |
| |
| /** |
| * The audio waveform filename |
| */ |
| private String mAudioWaveformFilename; |
| |
| /** |
| * The audio waveform data |
| */ |
| private SoftReference<WaveformData> mWaveformData; |
| |
| /** |
| * An object of this type cannot be instantiated by using the default |
| * constructor |
| */ |
| @SuppressWarnings("unused") |
| private AudioTrack() throws IOException { |
| this(null, null, null); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param editor The video editor reference |
| * @param audioTrackId The audio track id |
| * @param filename The absolute file name |
| * |
| * @throws IOException if file is not found |
| * @throws IllegalArgumentException if file format is not supported or if |
| * the codec is not supported or if editor is not of type |
| * VideoEditorImpl. |
| */ |
| public AudioTrack(VideoEditor editor, String audioTrackId, String filename) throws IOException { |
| this(editor, audioTrackId, filename, 0, 0, MediaItem.END_OF_FILE, false, 100, false, false, |
| 0, 0, null); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param editor The video editor reference |
| * @param audioTrackId The audio track id |
| * @param filename The audio filename. In case file contains Audio and Video, |
| * only the Audio stream will be used as Audio Track. |
| * @param startTimeMs the start time in milliseconds (relative to the |
| * timeline) |
| * @param beginMs start time in the audio track in milliseconds (relative to |
| * the beginning of the audio track) |
| * @param endMs end time in the audio track in milliseconds (relative to the |
| * beginning of the audio track) |
| * @param loop true to loop the audio track |
| * @param volume The volume in percentage |
| * @param muted true if the audio track is muted |
| * @param threshold Ducking will be activated when the relative energy in |
| * the media items audio signal goes above this value. The valid |
| * range of values is 0 to 90. |
| * @param duckedTrackVolume The relative volume of the audio track when |
| * ducking is active. The valid range of values is 0 to 100. |
| * @param audioWaveformFilename The name of the waveform file |
| * |
| * @throws IOException if file is not found |
| * @throws IllegalArgumentException if file format is not supported or if |
| * the codec is not supported or if editor is not of type |
| * VideoEditorImpl. |
| */ |
| AudioTrack(VideoEditor editor, String audioTrackId, String filename, |
| long startTimeMs,long beginMs, long endMs, boolean loop, |
| int volume, boolean muted,boolean duckingEnabled, |
| int duckThreshold, int duckedTrackVolume, |
| String audioWaveformFilename) throws IOException { |
| Properties properties = null; |
| |
| File file = new File(filename); |
| if (!file.exists()) { |
| throw new IOException(filename + " not found ! "); |
| } |
| |
| /*Compare file_size with 2GB*/ |
| if (VideoEditor.MAX_SUPPORTED_FILE_SIZE <= file.length()) { |
| throw new IllegalArgumentException("File size is more than 2GB"); |
| } |
| |
| if (editor instanceof VideoEditorImpl) { |
| mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext(); |
| } else { |
| throw new IllegalArgumentException("editor is not of type VideoEditorImpl"); |
| } |
| try { |
| properties = mMANativeHelper.getMediaProperties(filename); |
| } catch (Exception e) { |
| throw new IllegalArgumentException(e.getMessage() + " : " + filename); |
| } |
| int fileType = mMANativeHelper.getFileType(properties.fileType); |
| switch (fileType) { |
| case MediaProperties.FILE_3GP: |
| case MediaProperties.FILE_MP4: |
| case MediaProperties.FILE_MP3: |
| case MediaProperties.FILE_AMR: |
| break; |
| |
| default: { |
| throw new IllegalArgumentException("Unsupported input file type: " + fileType); |
| } |
| } |
| switch (mMANativeHelper.getAudioCodecType(properties.audioFormat)) { |
| case MediaProperties.ACODEC_AMRNB: |
| case MediaProperties.ACODEC_AMRWB: |
| case MediaProperties.ACODEC_AAC_LC: |
| case MediaProperties.ACODEC_MP3: |
| break; |
| default: |
| throw new IllegalArgumentException("Unsupported Audio Codec Format in Input File"); |
| } |
| |
| if (endMs == MediaItem.END_OF_FILE) { |
| endMs = properties.audioDuration; |
| } |
| |
| mUniqueId = audioTrackId; |
| mFilename = filename; |
| mStartTimeMs = startTimeMs; |
| mDurationMs = properties.audioDuration; |
| mAudioChannels = properties.audioChannels; |
| mAudioBitrate = properties.audioBitrate; |
| mAudioSamplingFrequency = properties.audioSamplingFrequency; |
| mAudioType = properties.audioFormat; |
| mTimelineDurationMs = endMs - beginMs; |
| mVolumePercent = volume; |
| |
| mBeginBoundaryTimeMs = beginMs; |
| mEndBoundaryTimeMs = endMs; |
| |
| mLoop = loop; |
| mMuted = muted; |
| mIsDuckingEnabled = duckingEnabled; |
| mDuckingThreshold = duckThreshold; |
| mDuckedTrackVolume = duckedTrackVolume; |
| |
| mAudioWaveformFilename = audioWaveformFilename; |
| if (audioWaveformFilename != null) { |
| mWaveformData = |
| new SoftReference<WaveformData>(new WaveformData(audioWaveformFilename)); |
| } else { |
| mWaveformData = null; |
| } |
| } |
| |
| /** |
| * Get the id of the audio track |
| * |
| * @return The id of the audio track |
| */ |
| public String getId() { |
| return mUniqueId; |
| } |
| |
| /** |
| * Get the filename for this audio track source. |
| * |
| * @return The filename as an absolute file name |
| */ |
| public String getFilename() { |
| return mFilename; |
| } |
| |
| /** |
| * Get the number of audio channels in the source of this audio track |
| * |
| * @return The number of audio channels in the source of this audio track |
| */ |
| public int getAudioChannels() { |
| return mAudioChannels; |
| } |
| |
| /** |
| * Get the audio codec of the source of this audio track |
| * |
| * @return The audio codec of the source of this audio track |
| * {@link android.media.videoeditor.MediaProperties} |
| */ |
| public int getAudioType() { |
| return mAudioType; |
| } |
| |
| /** |
| * Get the audio sample frequency of the audio track |
| * |
| * @return The audio sample frequency of the audio track |
| */ |
| public int getAudioSamplingFrequency() { |
| return mAudioSamplingFrequency; |
| } |
| |
| /** |
| * Get the audio bitrate of the audio track |
| * |
| * @return The audio bitrate of the audio track |
| */ |
| public int getAudioBitrate() { |
| return mAudioBitrate; |
| } |
| |
| /** |
| * Set the volume of this audio track as percentage of the volume in the |
| * original audio source file. |
| * |
| * @param volumePercent Percentage of the volume to apply. If it is set to |
| * 0, then volume becomes mute. It it is set to 100, then volume |
| * is same as original volume. It it is set to 200, then volume |
| * is doubled (provided that volume amplification is supported) |
| * |
| * @throws UnsupportedOperationException if volume amplification is |
| * requested and is not supported. |
| */ |
| public void setVolume(int volumePercent) { |
| if (volumePercent > MediaProperties.AUDIO_MAX_VOLUME_PERCENT) { |
| throw new IllegalArgumentException("Volume set exceeds maximum allowed value"); |
| } |
| |
| if (volumePercent < 0) { |
| throw new IllegalArgumentException("Invalid Volume "); |
| } |
| |
| /** |
| * Force update of preview settings |
| */ |
| mMANativeHelper.setGeneratePreview(true); |
| |
| mVolumePercent = volumePercent; |
| } |
| |
| /** |
| * Get the volume of the audio track as percentage of the volume in the |
| * original audio source file. |
| * |
| * @return The volume in percentage |
| */ |
| public int getVolume() { |
| return mVolumePercent; |
| } |
| |
| /** |
| * Mute/Unmute the audio track |
| * |
| * @param muted true to mute the audio track. SetMute(true) will make |
| * the volume of this Audio Track to 0. |
| */ |
| public void setMute(boolean muted) { |
| /** |
| * Force update of preview settings |
| */ |
| mMANativeHelper.setGeneratePreview(true); |
| mMuted = muted; |
| } |
| |
| /** |
| * Check if the audio track is muted |
| * |
| * @return true if the audio track is muted |
| */ |
| public boolean isMuted() { |
| return mMuted; |
| } |
| |
| /** |
| * Get the start time of this audio track relative to the storyboard |
| * timeline. |
| * |
| * @return The start time in milliseconds |
| */ |
| |
| public long getStartTime() { |
| return mStartTimeMs; |
| } |
| |
| /** |
| * Get the audio track duration |
| * |
| * @return The duration in milliseconds. This value represents actual audio |
| * track duration. This value is not effected by 'enableLoop' or |
| * 'setExtractBoundaries'. |
| */ |
| public long getDuration() { |
| return mDurationMs; |
| } |
| |
| /** |
| * Get the audio track timeline duration |
| * |
| * @return The timeline duration as defined by the begin and end boundaries |
| */ |
| public long getTimelineDuration() { |
| return mTimelineDurationMs; |
| } |
| |
| /** |
| * Sets the start and end marks for trimming an audio track |
| * |
| * @param beginMs start time in the audio track in milliseconds (relative to |
| * the beginning of the audio track) |
| * @param endMs end time in the audio track in milliseconds (relative to the |
| * beginning of the audio track) |
| */ |
| public void setExtractBoundaries(long beginMs, long endMs) { |
| if (beginMs > mDurationMs) { |
| throw new IllegalArgumentException("Invalid start time"); |
| } |
| if (endMs > mDurationMs) { |
| throw new IllegalArgumentException("Invalid end time"); |
| } |
| if (beginMs < 0) { |
| throw new IllegalArgumentException("Invalid start time; is < 0"); |
| } |
| if (endMs < 0) { |
| throw new IllegalArgumentException("Invalid end time; is < 0"); |
| } |
| |
| /** |
| * Force update of preview settings |
| */ |
| mMANativeHelper.setGeneratePreview(true); |
| |
| mBeginBoundaryTimeMs = beginMs; |
| mEndBoundaryTimeMs = endMs; |
| |
| mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; |
| } |
| |
| /** |
| * Get the boundary begin time |
| * |
| * @return The boundary begin time |
| */ |
| public long getBoundaryBeginTime() { |
| return mBeginBoundaryTimeMs; |
| } |
| |
| /** |
| * Get the boundary end time |
| * |
| * @return The boundary end time |
| */ |
| public long getBoundaryEndTime() { |
| return mEndBoundaryTimeMs; |
| } |
| |
| /** |
| * Enable the loop mode for this audio track. Note that only one of the |
| * audio tracks in the timeline can have the loop mode enabled. When looping |
| * is enabled the samples between mBeginBoundaryTimeMs and |
| * mEndBoundaryTimeMs are looped. |
| */ |
| public void enableLoop() { |
| if (!mLoop) { |
| /** |
| * Force update of preview settings |
| */ |
| mMANativeHelper.setGeneratePreview(true); |
| mLoop = true; |
| } |
| } |
| |
| /** |
| * Disable the loop mode |
| */ |
| public void disableLoop() { |
| if (mLoop) { |
| /** |
| * Force update of preview settings |
| */ |
| mMANativeHelper.setGeneratePreview(true); |
| mLoop = false; |
| } |
| } |
| |
| /** |
| * Check if looping is enabled |
| * |
| * @return true if looping is enabled |
| */ |
| public boolean isLooping() { |
| return mLoop; |
| } |
| |
| /** |
| * Disable the audio duck effect |
| */ |
| public void disableDucking() { |
| if (mIsDuckingEnabled) { |
| /** |
| * Force update of preview settings |
| */ |
| mMANativeHelper.setGeneratePreview(true); |
| mIsDuckingEnabled = false; |
| } |
| } |
| |
| /** |
| * Enable ducking by specifying the required parameters |
| * |
| * @param threshold Ducking will be activated when the energy in |
| * the media items audio signal goes above this value. The valid |
| * range of values is 0db to 90dB. 0dB is equivalent to disabling |
| * ducking. |
| * @param duckedTrackVolume The relative volume of the audio track when ducking |
| * is active. The valid range of values is 0 to 100. |
| */ |
| public void enableDucking(int threshold, int duckedTrackVolume) { |
| if (threshold < 0 || threshold > 90) { |
| throw new IllegalArgumentException("Invalid threshold value: " + threshold); |
| } |
| |
| if (duckedTrackVolume < 0 || duckedTrackVolume > 100) { |
| throw new IllegalArgumentException("Invalid duckedTrackVolume value: " |
| + duckedTrackVolume); |
| } |
| |
| /** |
| * Force update of preview settings |
| */ |
| mMANativeHelper.setGeneratePreview(true); |
| |
| mDuckingThreshold = threshold; |
| mDuckedTrackVolume = duckedTrackVolume; |
| mIsDuckingEnabled = true; |
| } |
| |
| /** |
| * Check if ducking is enabled |
| * |
| * @return true if ducking is enabled |
| */ |
| public boolean isDuckingEnabled() { |
| return mIsDuckingEnabled; |
| } |
| |
| /** |
| * Get the ducking threshold. |
| * |
| * @return The ducking threshold |
| */ |
| public int getDuckingThreshhold() { |
| return mDuckingThreshold; |
| } |
| |
| /** |
| * Get the ducked track volume. |
| * |
| * @return The ducked track volume |
| */ |
| public int getDuckedTrackVolume() { |
| return mDuckedTrackVolume; |
| } |
| |
| /** |
| * This API allows to generate a file containing the sample volume levels of |
| * this audio track object. This function may take significant time and is |
| * blocking. The filename can be retrieved using getAudioWaveformFilename(). |
| * |
| * @param listener The progress listener |
| * |
| * @throws IOException if the output file cannot be created |
| * @throws IllegalArgumentException if the audio file does not have a valid |
| * audio track |
| * @throws IllegalStateException if the codec type is unsupported |
| */ |
| public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) |
| throws IOException { |
| if (mAudioWaveformFilename == null) { |
| /** |
| * AudioWaveformFilename is generated |
| */ |
| final String projectPath = mMANativeHelper.getProjectPath(); |
| final String audioWaveFilename = String.format(projectPath + "/audioWaveformFile-" |
| + getId() + ".dat"); |
| |
| /** |
| * Logic to get frame duration = (no. of frames per sample * 1000)/ |
| * sampling frequency |
| */ |
| final int frameDuration; |
| final int sampleCount; |
| final int codecType = mMANativeHelper.getAudioCodecType(mAudioType); |
| switch (codecType) { |
| case MediaProperties.ACODEC_AMRNB: { |
| frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB * 1000) |
| / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; |
| sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB; |
| break; |
| } |
| |
| case MediaProperties.ACODEC_AMRWB: { |
| frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000) |
| / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; |
| sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB; |
| break; |
| } |
| |
| case MediaProperties.ACODEC_AAC_LC: { |
| frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000) |
| / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; |
| sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC; |
| break; |
| } |
| |
| case MediaProperties.ACODEC_MP3: { |
| frameDuration = (MediaProperties.SAMPLES_PER_FRAME_MP3 * 1000) |
| / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; |
| sampleCount = MediaProperties.SAMPLES_PER_FRAME_MP3; |
| break; |
| } |
| |
| default: { |
| throw new IllegalStateException("Unsupported codec type: " |
| + codecType); |
| } |
| } |
| |
| mMANativeHelper.generateAudioGraph( mUniqueId, |
| mFilename, |
| audioWaveFilename, |
| frameDuration, |
| MediaProperties.DEFAULT_CHANNEL_COUNT, |
| sampleCount, |
| listener, |
| false); |
| /** |
| * Record the generated file name |
| */ |
| mAudioWaveformFilename = audioWaveFilename; |
| } |
| mWaveformData = new SoftReference<WaveformData>(new WaveformData(mAudioWaveformFilename)); |
| } |
| |
| /** |
| * Get the audio waveform file name if extractAudioWaveform was successful. |
| * |
| * @return the name of the file, null if the file does not exist |
| */ |
| String getAudioWaveformFilename() { |
| return mAudioWaveformFilename; |
| } |
| |
| /** |
| * Delete the waveform file |
| */ |
| void invalidate() { |
| if (mAudioWaveformFilename != null) { |
| new File(mAudioWaveformFilename).delete(); |
| mAudioWaveformFilename = null; |
| mWaveformData = null; |
| } |
| } |
| |
| /** |
| * Get the audio waveform data. |
| * |
| * @return The waveform data |
| * |
| * @throws IOException if the waveform file cannot be found |
| */ |
| public WaveformData getWaveformData() throws IOException { |
| if (mWaveformData == null) { |
| return null; |
| } |
| |
| WaveformData waveformData = mWaveformData.get(); |
| if (waveformData != null) { |
| return waveformData; |
| } else if (mAudioWaveformFilename != null) { |
| try { |
| waveformData = new WaveformData(mAudioWaveformFilename); |
| } catch (IOException e) { |
| throw e; |
| } |
| mWaveformData = new SoftReference<WaveformData>(waveformData); |
| return waveformData; |
| } else { |
| return null; |
| } |
| } |
| |
| /* |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean equals(Object object) { |
| if (!(object instanceof AudioTrack)) { |
| return false; |
| } |
| return mUniqueId.equals(((AudioTrack)object).mUniqueId); |
| } |
| |
| /* |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| return mUniqueId.hashCode(); |
| } |
| } |