blob: c5cc2cad05deaa2dbd8db92293c2f8a88fe27047 [file] [log] [blame]
/*
* 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();
}
}