Initial Video Editor API
Change-Id: Iaa91e78d0e50f45ceb943bab93c4f1ea1bdee003
This commit is contained in:
326
media/java/android/media/videoeditor/AudioTrack.java
Executable file
326
media/java/android/media/videoeditor/AudioTrack.java
Executable file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
|
||||
/**
|
||||
* This class allows to handle an audio track. This audio file is mixed with the
|
||||
* audio samples of the MediaItems.
|
||||
* {@hide}
|
||||
*/
|
||||
public class AudioTrack {
|
||||
// Instance variables
|
||||
private final String mUniqueId;
|
||||
private final String mFilename;
|
||||
private final long mDurationMs;
|
||||
private long mStartTimeMs;
|
||||
private long mTimelineDurationMs;
|
||||
private int mVolumePercent;
|
||||
private long mBeginBoundaryTimeMs;
|
||||
private long mEndBoundaryTimeMs;
|
||||
private boolean mLoop;
|
||||
|
||||
// Ducking variables
|
||||
private int mDuckingThreshold;
|
||||
private int mDuckingLowVolume;
|
||||
private boolean mIsDuckingEnabled;
|
||||
|
||||
// The audio waveform filename
|
||||
private String mAudioWaveformFilename;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private AudioTrack() throws IOException {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param audioTrackId The AudioTrack 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
|
||||
*/
|
||||
public AudioTrack(String audioTrackId, String filename) throws IOException {
|
||||
mUniqueId = audioTrackId;
|
||||
mFilename = filename;
|
||||
mStartTimeMs = 0;
|
||||
// TODO: This value represents to the duration of the audio file
|
||||
mDurationMs = 300000;
|
||||
mTimelineDurationMs = mDurationMs;
|
||||
mVolumePercent = 100;
|
||||
|
||||
// Play the entire audio track
|
||||
mBeginBoundaryTimeMs = 0;
|
||||
mEndBoundaryTimeMs = mDurationMs;
|
||||
|
||||
// By default loop is disabled
|
||||
mLoop = false;
|
||||
|
||||
// Ducking is enabled by default
|
||||
mDuckingThreshold = 0;
|
||||
mDuckingLowVolume = 0;
|
||||
mIsDuckingEnabled = true;
|
||||
|
||||
// The audio waveform file is generated later
|
||||
mAudioWaveformFilename = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The id of the media item
|
||||
*/
|
||||
public String getId() {
|
||||
return mUniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename source for this audio track.
|
||||
*
|
||||
* @return The filename as an absolute file name
|
||||
*/
|
||||
public String getFilename() {
|
||||
return mFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start time of this audio track relative to the storyboard
|
||||
* timeline. Default value is 0.
|
||||
*
|
||||
* @param startTimeMs the start time in milliseconds
|
||||
*/
|
||||
public void setStartTime(long startTimeMs) {
|
||||
mStartTimeMs = startTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start time of this audio track relative to the storyboard
|
||||
* timeline.
|
||||
*
|
||||
* @return The start time in milliseconds
|
||||
*/
|
||||
public long getStartTime() {
|
||||
return mStartTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The duration in milliseconds. This value represents the audio
|
||||
* track duration (not looped)
|
||||
*/
|
||||
public long getDuration() {
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The timeline duration. If looping is enabled this value
|
||||
* represents the duration of the looped audio track, otherwise it
|
||||
* is the duration of the audio track (mDurationMs).
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
mBeginBoundaryTimeMs = beginMs;
|
||||
mEndBoundaryTimeMs = endMs;
|
||||
if (mLoop) {
|
||||
// TODO: Compute mDurationMs (from the beginning of the loop until
|
||||
// the end of all the loops.
|
||||
mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
|
||||
} else {
|
||||
mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The boundary begin time
|
||||
*/
|
||||
public long getBoundaryBeginTime() {
|
||||
return mBeginBoundaryTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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() {
|
||||
mLoop = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the loop mode
|
||||
*/
|
||||
public void disableLoop() {
|
||||
mLoop = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if looping is enabled
|
||||
*/
|
||||
public boolean isLooping() {
|
||||
return mLoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the audio duck effect
|
||||
*/
|
||||
public void disableDucking() {
|
||||
mIsDuckingEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO DEFINE
|
||||
*
|
||||
* @param threshold
|
||||
* @param lowVolume
|
||||
* @param volume
|
||||
*/
|
||||
public void enableDucking(int threshold, int lowVolume, int volume) {
|
||||
mDuckingThreshold = threshold;
|
||||
mDuckingLowVolume = lowVolume;
|
||||
mIsDuckingEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if ducking is enabled
|
||||
*/
|
||||
public boolean isDuckingEnabled() {
|
||||
return mIsDuckingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The ducking threshold
|
||||
*/
|
||||
public int getDuckingThreshhold() {
|
||||
return mDuckingThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The ducking low level
|
||||
*/
|
||||
public int getDuckingLowVolume() {
|
||||
return mDuckingLowVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
|
||||
throws IOException {
|
||||
// TODO: Set mAudioWaveformFilename at the end once the extract is complete
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio waveform file name if extractAudioWaveform was successful.
|
||||
* The file format is as following:
|
||||
* <ul>
|
||||
* <li>first 4 bytes provide the number of samples for each value, as
|
||||
* big-endian signed</li>
|
||||
* <li>4 following bytes is the total number of values in the file, as
|
||||
* big-endian signed</li>
|
||||
* <li>then, all values follow as bytes</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the name of the file, null if the file does not exist
|
||||
*/
|
||||
public String getAudioWaveformFilename() {
|
||||
return mAudioWaveformFilename;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@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();
|
||||
}
|
||||
}
|
||||
119
media/java/android/media/videoeditor/Effect.java
Executable file
119
media/java/android/media/videoeditor/Effect.java
Executable file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This is the super class for all effects. An effect can only be applied to a
|
||||
* single media item. If one wants to apply the same effect to multiple media
|
||||
* items, multiple @{MediaItem.addEffect(Effect)} call must be invoked on each
|
||||
* of the MediaItem objects.
|
||||
* {@hide}
|
||||
*/
|
||||
public abstract class Effect {
|
||||
// Instance variables
|
||||
private final String mUniqueId;
|
||||
protected long mDurationMs;
|
||||
// The start time of the effect relative to the media item timeline
|
||||
protected long mStartTimeMs;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private Effect() {
|
||||
mUniqueId = null;
|
||||
mStartTimeMs = 0;
|
||||
mDurationMs = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param effectId The effect id
|
||||
* @param startTimeMs The start time relative to the media item to which it
|
||||
* is applied
|
||||
* @param durationMs The effect duration in milliseconds
|
||||
*/
|
||||
public Effect(String effectId, long startTimeMs, long durationMs) {
|
||||
mUniqueId = effectId;
|
||||
mStartTimeMs = startTimeMs;
|
||||
mDurationMs = durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The id of the effect
|
||||
*/
|
||||
public String getId() {
|
||||
return mUniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the duration of the effect. If a preview or export is in progress,
|
||||
* then this change is effective for next preview or export session. s
|
||||
*
|
||||
* @param durationMs of the effect in milliseconds
|
||||
*/
|
||||
public void setDuration(long durationMs) {
|
||||
mDurationMs = durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration of the effect
|
||||
*
|
||||
* @return The duration of the effect in milliseconds
|
||||
*/
|
||||
public long getDuration() {
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set start time of the effect. If a preview or export is in progress, then
|
||||
* this change is effective for next preview or export session.
|
||||
*
|
||||
* @param startTimeMs The start time of the effect relative to the begining
|
||||
* of the media item in milliseconds
|
||||
*/
|
||||
public void setStartTime(long startTimeMs) {
|
||||
mStartTimeMs = startTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The start time in milliseconds
|
||||
*/
|
||||
public long getStartTime() {
|
||||
return mStartTimeMs;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof Effect)) {
|
||||
return false;
|
||||
}
|
||||
return mUniqueId.equals(((Effect)object).mUniqueId);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mUniqueId.hashCode();
|
||||
}
|
||||
}
|
||||
100
media/java/android/media/videoeditor/EffectColor.java
Executable file
100
media/java/android/media/videoeditor/EffectColor.java
Executable file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This class allows to apply color on a media item.
|
||||
* {@hide}
|
||||
*/
|
||||
public class EffectColor extends Effect {
|
||||
|
||||
/**
|
||||
* Change the video frame color to the RGB color value provided
|
||||
*/
|
||||
public static final int TYPE_COLOR = 1; // color as 888 RGB
|
||||
/**
|
||||
* Change the video frame color to a gradation from RGB color (at the top of
|
||||
* the frame) to black (at the bottom of the frame).
|
||||
*/
|
||||
public static final int TYPE_GRADIENT = 2;
|
||||
/**
|
||||
* Change the video frame color to sepia
|
||||
*/
|
||||
public static final int TYPE_SEPIA = 3;
|
||||
/**
|
||||
* Invert the video frame color
|
||||
*/
|
||||
public static final int TYPE_NEGATIVE = 4;
|
||||
/**
|
||||
* Make the video look like as if it was recorded in 50's
|
||||
*/
|
||||
public static final int TYPE_FIFTIES = 5;
|
||||
|
||||
// Colors for the color effect
|
||||
public static final int GREEN = 0x0000ff00;
|
||||
public static final int PINK = 0x00ff66cc;
|
||||
public static final int GRAY = 0x007f7f7f;
|
||||
|
||||
// The effect type
|
||||
private final int mType;
|
||||
|
||||
// The effect parameter
|
||||
private final int mParam;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private EffectColor() {
|
||||
this(null, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param effectId The effect id
|
||||
* @param startTimeMs The start time relative to the media item to which it
|
||||
* is applied
|
||||
* @param durationMs The duration of this effect in milliseconds
|
||||
* @param type type of the effect. type is one of: TYPE_COLOR,
|
||||
* TYPE_GRADIENT, TYPE_SEPIA, TYPE_NEGATIVE, TYPE_FIFTIES. If
|
||||
* type is not supported, the argument is ignored
|
||||
* @param param if type is TYPE_COLOR, param is the RGB color as 888.
|
||||
* Otherwise, param is ignored
|
||||
*/
|
||||
public EffectColor(String effectId, long startTimeMs, long durationMs,
|
||||
int type, int param) {
|
||||
super(effectId, startTimeMs, durationMs);
|
||||
mType = type;
|
||||
mParam = param;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The type of this effect
|
||||
*/
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the color as RGB 888 if type is TYPE_COLOR. Otherwise, ignore.
|
||||
*/
|
||||
public int getParam() {
|
||||
return mParam;
|
||||
}
|
||||
}
|
||||
90
media/java/android/media/videoeditor/EffectKenBurns.java
Executable file
90
media/java/android/media/videoeditor/EffectKenBurns.java
Executable file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
/**
|
||||
* This class represents a Ken Burns effect.
|
||||
* {@hide}
|
||||
*/
|
||||
public class EffectKenBurns extends Effect {
|
||||
// Instance variables
|
||||
private Rect mStartRect;
|
||||
private Rect mEndRect;
|
||||
|
||||
/**
|
||||
* Objects of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private EffectKenBurns() throws IOException {
|
||||
this(null, null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param effectId The effect id
|
||||
* @param startRect The start rectangle
|
||||
* @param endRect The end rectangle
|
||||
* @param startTimeMs The start time
|
||||
* @param durationMs The duration of the Ken Burns effect in milliseconds
|
||||
*/
|
||||
public EffectKenBurns(String effectId, Rect startRect, Rect endRect, long startTime,
|
||||
long durationMs)
|
||||
throws IOException {
|
||||
super(effectId, startTime, durationMs);
|
||||
|
||||
mStartRect = startRect;
|
||||
mEndRect = endRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startRect The start rectangle
|
||||
*
|
||||
* @throws IllegalArgumentException if start rectangle is incorrectly set.
|
||||
*/
|
||||
public void setStartRect(Rect startRect) {
|
||||
mStartRect = startRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The start rectangle
|
||||
*/
|
||||
public Rect getStartRect() {
|
||||
return mStartRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param endRect The end rectangle
|
||||
*
|
||||
* @throws IllegalArgumentException if end rectangle is incorrectly set.
|
||||
*/
|
||||
public void setEndRect(Rect endRect) {
|
||||
mEndRect = endRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The end rectangle
|
||||
*/
|
||||
public Rect getEndRect() {
|
||||
return mEndRect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This listener interface is used by
|
||||
* {@link MediaVideoItem#extractAudioWaveform(ExtractAudioWaveformProgressListener listener)}
|
||||
* or
|
||||
* {@link AudioTrack#extractAudioWaveform(ExtractAudioWaveformProgressListener listener)}
|
||||
* {@hide}
|
||||
*/
|
||||
public interface ExtractAudioWaveformProgressListener {
|
||||
/**
|
||||
* This method notifies the listener of the progress status of
|
||||
* an extractAudioWaveform operation.
|
||||
* This method may be called maximum 100 times for one operation.
|
||||
*
|
||||
* @param progress The progress in %. At the beginning of the operation,
|
||||
* this value is set to 0; at the end, the value is set to 100.
|
||||
*/
|
||||
public void onProgress(int progress);
|
||||
}
|
||||
|
||||
227
media/java/android/media/videoeditor/MediaImageItem.java
Executable file
227
media/java/android/media/videoeditor/MediaImageItem.java
Executable file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class represents an image item on the storyboard.
|
||||
* {@hide}
|
||||
*/
|
||||
public class MediaImageItem extends MediaItem {
|
||||
// Logging
|
||||
private static final String TAG = "MediaImageItem";
|
||||
|
||||
// The resize paint
|
||||
private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
|
||||
// Instance variables
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final int mAspectRatio;
|
||||
private long mDurationMs;
|
||||
|
||||
/**
|
||||
* This class cannot be instantiated by using the default constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private MediaImageItem() throws IOException {
|
||||
this(null, null, 0, RENDERING_MODE_BLACK_BORDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param mediaItemId The MediaItem id
|
||||
* @param filename The image file name
|
||||
* @param durationMs The duration of the image on the storyboard
|
||||
* @param renderingMode The rendering mode
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public MediaImageItem(String mediaItemId, String filename, long durationMs, int renderingMode)
|
||||
throws IOException {
|
||||
super(mediaItemId, filename, renderingMode);
|
||||
|
||||
// Determine the size of the image
|
||||
final BitmapFactory.Options dbo = new BitmapFactory.Options();
|
||||
dbo.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(filename, dbo);
|
||||
|
||||
mWidth = dbo.outWidth;
|
||||
mHeight = dbo.outHeight;
|
||||
mDurationMs = durationMs;
|
||||
|
||||
// TODO: Determine the aspect ratio from the width and height
|
||||
mAspectRatio = MediaProperties.ASPECT_RATIO_4_3;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getFileType() {
|
||||
if (mFilename.endsWith(".jpg") || mFilename.endsWith(".jpeg")) {
|
||||
return MediaProperties.FILE_JPEG;
|
||||
} else if (mFilename.endsWith(".png")) {
|
||||
return MediaProperties.FILE_PNG;
|
||||
} else {
|
||||
return MediaProperties.FILE_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getAspectRatio() {
|
||||
return mAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param durationMs The duration of the image in the storyboard timeline
|
||||
*/
|
||||
public void setDuration(long durationMs) {
|
||||
mDurationMs = durationMs;
|
||||
// TODO: Validate/modify the start and the end time of effects and overlays
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long getTimelineDuration() {
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Bitmap getThumbnail(int width, int height, long timeMs) throws IOException {
|
||||
return generateImageThumbnail(mFilename, width, height);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
|
||||
int thumbnailCount) throws IOException {
|
||||
final Bitmap thumbnail = generateImageThumbnail(mFilename, width, height);
|
||||
final Bitmap[] thumbnailArray = new Bitmap[thumbnailCount];
|
||||
for (int i = 0; i < thumbnailCount; i++) {
|
||||
thumbnailArray[i] = thumbnail;
|
||||
}
|
||||
return thumbnailArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize a bitmap within an input stream
|
||||
*
|
||||
* @param filename The filename
|
||||
* @param width The thumbnail width
|
||||
* @param height The thumbnail height
|
||||
*
|
||||
* @return The resized bitmap
|
||||
*/
|
||||
private Bitmap generateImageThumbnail(String filename, int width, int height)
|
||||
throws IOException {
|
||||
final BitmapFactory.Options dbo = new BitmapFactory.Options();
|
||||
dbo.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(filename, dbo);
|
||||
|
||||
final int nativeWidth = dbo.outWidth;
|
||||
final int nativeHeight = dbo.outHeight;
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "generateThumbnail: Input: " + nativeWidth + "x" + nativeHeight
|
||||
+ ", resize to: " + width + "x" + height);
|
||||
}
|
||||
|
||||
final Bitmap srcBitmap;
|
||||
float bitmapWidth, bitmapHeight;
|
||||
if (nativeWidth > width || nativeHeight > height) {
|
||||
float dx = ((float)nativeWidth) / ((float)width);
|
||||
float dy = ((float)nativeHeight) / ((float)height);
|
||||
if (dx > dy) {
|
||||
bitmapWidth = width;
|
||||
bitmapHeight = nativeHeight / dx;
|
||||
} else {
|
||||
bitmapWidth = nativeWidth / dy;
|
||||
bitmapHeight = height;
|
||||
}
|
||||
// Create the bitmap from file
|
||||
if (nativeWidth / bitmapWidth > 1) {
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = nativeWidth / (int)bitmapWidth;
|
||||
srcBitmap = BitmapFactory.decodeFile(filename, options);
|
||||
} else {
|
||||
srcBitmap = BitmapFactory.decodeFile(filename);
|
||||
}
|
||||
} else {
|
||||
bitmapWidth = width;
|
||||
bitmapHeight = height;
|
||||
srcBitmap = BitmapFactory.decodeFile(filename);
|
||||
}
|
||||
|
||||
if (srcBitmap == null) {
|
||||
Log.e(TAG, "generateThumbnail: Cannot decode image bytes");
|
||||
throw new IOException("Cannot decode file: " + mFilename);
|
||||
}
|
||||
|
||||
// Create the canvas bitmap
|
||||
final Bitmap bitmap = Bitmap.createBitmap((int)bitmapWidth, (int)bitmapHeight,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawBitmap(srcBitmap, new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight()),
|
||||
new Rect(0, 0, (int)bitmapWidth, (int)bitmapHeight), sResizePaint);
|
||||
// Release the source bitmap
|
||||
srcBitmap.recycle();
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
434
media/java/android/media/videoeditor/MediaItem.java
Executable file
434
media/java/android/media/videoeditor/MediaItem.java
Executable file
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/**
|
||||
* This abstract class describes the base class for any MediaItem. Objects are
|
||||
* defined with a file path as a source data.
|
||||
* {@hide}
|
||||
s */
|
||||
public abstract class MediaItem {
|
||||
// A constant which can be used to specify the end of the file (instead of
|
||||
// providing the actual duration of the media item).
|
||||
public final static int END_OF_FILE = -1;
|
||||
|
||||
// Rendering modes
|
||||
/**
|
||||
* When using the RENDERING_MODE_BLACK_BORDER rendering mode video frames
|
||||
* are resized by preserving the aspect ratio until the movie matches one of
|
||||
* the dimensions of the output movie. The areas outside the resized video
|
||||
* clip are rendered black.
|
||||
*/
|
||||
public static final int RENDERING_MODE_BLACK_BORDER = 0;
|
||||
/**
|
||||
* When using the RENDERING_MODE_STRETCH rendering mode video frames are
|
||||
* stretched horizontally or vertically to match the current aspect ratio of
|
||||
* the movie.
|
||||
*/
|
||||
public static final int RENDERING_MODE_STRETCH = 1;
|
||||
|
||||
|
||||
// The unique id of the MediaItem
|
||||
private final String mUniqueId;
|
||||
|
||||
// The name of the file associated with the MediaItem
|
||||
protected final String mFilename;
|
||||
|
||||
// List of effects
|
||||
private final List<Effect> mEffects;
|
||||
|
||||
// List of overlays
|
||||
private final List<Overlay> mOverlays;
|
||||
|
||||
// The rendering mode
|
||||
private int mRenderingMode;
|
||||
|
||||
// Beginning and end transitions
|
||||
private Transition mBeginTransition;
|
||||
private Transition mEndTransition;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param mediaItemId The MediaItem id
|
||||
* @param filename name of the media file.
|
||||
* @param renderingMode The rendering mode
|
||||
*
|
||||
* @throws IOException if file is not found
|
||||
* @throws IllegalArgumentException if a capability such as file format is not
|
||||
* supported the exception object contains the unsupported
|
||||
* capability
|
||||
*/
|
||||
protected MediaItem(String mediaItemId, String filename, int renderingMode) throws IOException {
|
||||
mUniqueId = mediaItemId;
|
||||
mFilename = filename;
|
||||
mRenderingMode = renderingMode;
|
||||
mEffects = new ArrayList<Effect>();
|
||||
mOverlays = new ArrayList<Overlay>();
|
||||
mBeginTransition = null;
|
||||
mEndTransition = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The of the media item
|
||||
*/
|
||||
public String getId() {
|
||||
return mUniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The media source file name
|
||||
*/
|
||||
public String getFilename() {
|
||||
return mFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* If aspect ratio of the MediaItem is different from the aspect ratio of
|
||||
* the editor then this API controls the rendering mode.
|
||||
*
|
||||
* @param renderingMode rendering mode. It is one of:
|
||||
* {@link #RENDERING_MODE_BLACK_BORDER},
|
||||
* {@link #RENDERING_MODE_STRETCH}
|
||||
*/
|
||||
public void setRenderingMode(int renderingMode) {
|
||||
mRenderingMode = renderingMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The rendering mode
|
||||
*/
|
||||
public int getRenderingMode() {
|
||||
return mRenderingMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param transition The beginning transition
|
||||
*/
|
||||
void setBeginTransition(Transition transition) {
|
||||
mBeginTransition = transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The begin transition
|
||||
*/
|
||||
public Transition getBeginTransition() {
|
||||
return mBeginTransition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param transition The end transition
|
||||
*/
|
||||
void setEndTransition(Transition transition) {
|
||||
mEndTransition = transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The end transition
|
||||
*/
|
||||
public Transition getEndTransition() {
|
||||
return mEndTransition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The duration of the media item
|
||||
*/
|
||||
public abstract long getDuration();
|
||||
|
||||
/**
|
||||
* @return The timeline duration. This is the actual duration in the
|
||||
* timeline (trimmed duration)
|
||||
*/
|
||||
public abstract long getTimelineDuration();
|
||||
|
||||
/**
|
||||
* @return The source file type
|
||||
*/
|
||||
public abstract int getFileType();
|
||||
|
||||
/**
|
||||
* @return Get the native width of the media item
|
||||
*/
|
||||
public abstract int getWidth();
|
||||
|
||||
/**
|
||||
* @return Get the native height of the media item
|
||||
*/
|
||||
public abstract int getHeight();
|
||||
|
||||
/**
|
||||
* Get aspect ratio of the source media item.
|
||||
*
|
||||
* @return the aspect ratio as described in MediaProperties.
|
||||
* MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not
|
||||
* supported as in MediaProperties
|
||||
*/
|
||||
public abstract int getAspectRatio();
|
||||
|
||||
/**
|
||||
* Add the specified effect to this media item.
|
||||
*
|
||||
* Note that certain types of effects cannot be applied to video and to
|
||||
* image media items. For example in certain implementation a Ken Burns
|
||||
* implementation cannot be applied to video media item.
|
||||
*
|
||||
* This method invalidates transition video clips if the
|
||||
* effect overlaps with the beginning and/or the end transition.
|
||||
*
|
||||
* @param effect The effect to apply
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if the effect start and/or duration are
|
||||
* invalid or if the effect cannot be applied to this type of media
|
||||
* item or if the effect id is not unique across all the Effects
|
||||
* added.
|
||||
*/
|
||||
public void addEffect(Effect effect) {
|
||||
if (mEffects.contains(effect)) {
|
||||
throw new IllegalArgumentException("Effect already exists: " + effect.getId());
|
||||
}
|
||||
|
||||
if (effect.getStartTime() + effect.getDuration() > getDuration()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Effect start time + effect duration > media clip duration");
|
||||
}
|
||||
|
||||
mEffects.add(effect);
|
||||
invalidateTransitions(effect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the effect with the specified id.
|
||||
*
|
||||
* This method invalidates a transition video clip if the effect overlaps
|
||||
* with a transition.
|
||||
*
|
||||
* @param effectId The id of the effect to be removed
|
||||
*
|
||||
* @return The effect that was removed
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
*/
|
||||
public Effect removeEffect(String effectId) {
|
||||
for (Effect effect : mEffects) {
|
||||
if (effect.getId().equals(effectId)) {
|
||||
mEffects.remove(effect);
|
||||
invalidateTransitions(effect);
|
||||
return effect;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the effect with the specified id
|
||||
*
|
||||
* @param effectId The effect id
|
||||
*
|
||||
* @return The effect with the specified id (null if it does not exist)
|
||||
*/
|
||||
public Effect getEffect(String effectId) {
|
||||
for (Effect effect : mEffects) {
|
||||
if (effect.getId().equals(effectId)) {
|
||||
return effect;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of effects.
|
||||
*
|
||||
* @return the effects list. If no effects exist an empty list will be returned.
|
||||
*/
|
||||
public List<Effect> getAllEffects() {
|
||||
return mEffects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an overlay to the storyboard. This method invalidates a transition
|
||||
* video clip if the overlay overlaps with a transition.
|
||||
*
|
||||
* @param overlay The overlay to add
|
||||
* @throws IllegalStateException if a preview or an export is in progress or
|
||||
* if the overlay id is not unique across all the overlays added.
|
||||
*/
|
||||
public void addOverlay(Overlay overlay) {
|
||||
if (mOverlays.contains(overlay)) {
|
||||
throw new IllegalArgumentException("Overlay already exists: " + overlay.getId());
|
||||
}
|
||||
|
||||
if (overlay.getStartTime() + overlay.getDuration() > getDuration()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Overlay start time + overlay duration > media clip duration");
|
||||
}
|
||||
|
||||
mOverlays.add(overlay);
|
||||
invalidateTransitions(overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the overlay with the specified id.
|
||||
*
|
||||
* This method invalidates a transition video clip if the overlay overlaps
|
||||
* with a transition.
|
||||
*
|
||||
* @param overlayId The id of the overlay to be removed
|
||||
*
|
||||
* @return The overlay that was removed
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
*/
|
||||
public Overlay removeOverlay(String overlayId) {
|
||||
for (Overlay overlay : mOverlays) {
|
||||
if (overlay.getId().equals(overlayId)) {
|
||||
mOverlays.remove(overlay);
|
||||
invalidateTransitions(overlay);
|
||||
return overlay;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the overlay with the specified id
|
||||
*
|
||||
* @param overlayId The overlay id
|
||||
*
|
||||
* @return The overlay with the specified id (null if it does not exist)
|
||||
*/
|
||||
public Overlay getOverlay(String overlayId) {
|
||||
for (Overlay overlay : mOverlays) {
|
||||
if (overlay.getId().equals(overlayId)) {
|
||||
return overlay;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of overlays associated with this media item
|
||||
*
|
||||
* Note that if any overlay source files are not accessible anymore,
|
||||
* this method will still provide the full list of overlays.
|
||||
*
|
||||
* @return The list of overlays. If no overlays exist an empty list will
|
||||
* be returned.
|
||||
*/
|
||||
public List<Overlay> getAllOverlays() {
|
||||
return mOverlays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a thumbnail at specified time in a video stream in Bitmap format
|
||||
*
|
||||
* @param width width of the thumbnail in pixels
|
||||
* @param height height of the thumbnail in pixels
|
||||
* @param timeMs The time in the source video file at which the thumbnail is
|
||||
* requested (even if trimmed).
|
||||
*
|
||||
* @return The thumbnail as a Bitmap.
|
||||
*
|
||||
* @throws IOException if a file error occurs
|
||||
* @throws IllegalArgumentException if time is out of video duration
|
||||
*/
|
||||
public abstract Bitmap getThumbnail(int width, int height, long timeMs) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the array of Bitmap thumbnails between start and end.
|
||||
*
|
||||
* @param width width of the thumbnail in pixels
|
||||
* @param height height of the thumbnail in pixels
|
||||
* @param startMs The start of time range in milliseconds
|
||||
* @param endMs The end of the time range in milliseconds
|
||||
* @param thumbnailCount The thumbnail count
|
||||
*
|
||||
* @return The array of Bitmaps
|
||||
*
|
||||
* @throws IOException if a file error occurs
|
||||
*/
|
||||
public abstract Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
|
||||
int thumbnailCount) throws IOException;
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof MediaItem)) {
|
||||
return false;
|
||||
}
|
||||
return mUniqueId.equals(((MediaItem)object).mUniqueId);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mUniqueId.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the start and end transitions if necessary
|
||||
*
|
||||
* @param effect The effect that was added or removed
|
||||
*/
|
||||
private void invalidateTransitions(Effect effect) {
|
||||
// Check if the effect overlaps with the beginning and end transitions
|
||||
if (mBeginTransition != null) {
|
||||
if (effect.getStartTime() < mBeginTransition.getDuration()) {
|
||||
mBeginTransition.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
if (mEndTransition != null) {
|
||||
if (effect.getStartTime() + effect.getDuration() > getDuration()
|
||||
- mEndTransition.getDuration()) {
|
||||
mEndTransition.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the start and end transitions if necessary
|
||||
*
|
||||
* @param overlay The effect that was added or removed
|
||||
*/
|
||||
private void invalidateTransitions(Overlay overlay) {
|
||||
// Check if the overlay overlaps with the beginning and end transitions
|
||||
if (mBeginTransition != null) {
|
||||
if (overlay.getStartTime() < mBeginTransition.getDuration()) {
|
||||
mBeginTransition.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
if (mEndTransition != null) {
|
||||
if (overlay.getStartTime() + overlay.getDuration() > getDuration()
|
||||
- mEndTransition.getDuration()) {
|
||||
mEndTransition.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
256
media/java/android/media/videoeditor/MediaProperties.java
Executable file
256
media/java/android/media/videoeditor/MediaProperties.java
Executable file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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 android.util.Pair;
|
||||
|
||||
/**
|
||||
* This class defines all properties of a media file such as supported height, aspect ratio,
|
||||
* bitrate for export function.
|
||||
* {@hide}
|
||||
*/
|
||||
public class MediaProperties {
|
||||
// Supported heights
|
||||
public static final int HEIGHT_144 = 144;
|
||||
public static final int HEIGHT_360 = 360;
|
||||
public static final int HEIGHT_480 = 480;
|
||||
public static final int HEIGHT_720 = 720;
|
||||
|
||||
// Supported aspect ratios
|
||||
public static final int ASPECT_RATIO_UNDEFINED = 0;
|
||||
public static final int ASPECT_RATIO_3_2 = 1;
|
||||
public static final int ASPECT_RATIO_16_9 = 2;
|
||||
public static final int ASPECT_RATIO_4_3 = 3;
|
||||
public static final int ASPECT_RATIO_5_3 = 4;
|
||||
public static final int ASPECT_RATIO_11_9 = 5;
|
||||
|
||||
// The array of supported aspect ratios
|
||||
private static final int[] ASPECT_RATIOS = new int[] {
|
||||
ASPECT_RATIO_3_2,
|
||||
ASPECT_RATIO_16_9,
|
||||
ASPECT_RATIO_4_3,
|
||||
ASPECT_RATIO_5_3,
|
||||
ASPECT_RATIO_11_9
|
||||
};
|
||||
|
||||
// Supported resolutions for specific aspect ratios
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private static final Pair<Integer, Integer>[] ASPECT_RATIO_3_2_RESOLUTIONS =
|
||||
new Pair[] {
|
||||
new Pair<Integer, Integer>(720, HEIGHT_480),
|
||||
new Pair<Integer, Integer>(1080, HEIGHT_720)
|
||||
};
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private static final Pair<Integer, Integer>[] ASPECT_RATIO_4_3_RESOLUTIONS =
|
||||
new Pair[] {
|
||||
new Pair<Integer, Integer>(640, HEIGHT_480),
|
||||
new Pair<Integer, Integer>(960, HEIGHT_720)
|
||||
};
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private static final Pair<Integer, Integer>[] ASPECT_RATIO_5_3_RESOLUTIONS =
|
||||
new Pair[] {
|
||||
new Pair<Integer, Integer>(800, HEIGHT_480)
|
||||
};
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private static final Pair<Integer, Integer>[] ASPECT_RATIO_11_9_RESOLUTIONS =
|
||||
new Pair[] {
|
||||
new Pair<Integer, Integer>(176, HEIGHT_144)
|
||||
};
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private static final Pair<Integer, Integer>[] ASPECT_RATIO_16_9_RESOLUTIONS =
|
||||
new Pair[] {
|
||||
new Pair<Integer, Integer>(640, HEIGHT_360),
|
||||
new Pair<Integer, Integer>(854, HEIGHT_480),
|
||||
new Pair<Integer, Integer>(1280, HEIGHT_720),
|
||||
};
|
||||
|
||||
|
||||
// Bitrate values (in bits per second)
|
||||
public static final int BITRATE_28K = 28000;
|
||||
public static final int BITRATE_40K = 40000;
|
||||
public static final int BITRATE_64K = 64000;
|
||||
public static final int BITRATE_96K = 96000;
|
||||
public static final int BITRATE_128K = 128000;
|
||||
public static final int BITRATE_192K = 192000;
|
||||
public static final int BITRATE_256K = 256000;
|
||||
public static final int BITRATE_384K = 384000;
|
||||
public static final int BITRATE_512K = 512000;
|
||||
public static final int BITRATE_800K = 800000;
|
||||
|
||||
// The array of supported bitrates
|
||||
private static final int[] SUPPORTED_BITRATES = new int[] {
|
||||
BITRATE_28K,
|
||||
BITRATE_40K,
|
||||
BITRATE_64K,
|
||||
BITRATE_96K,
|
||||
BITRATE_128K,
|
||||
BITRATE_192K,
|
||||
BITRATE_256K,
|
||||
BITRATE_384K,
|
||||
BITRATE_512K,
|
||||
BITRATE_800K
|
||||
};
|
||||
|
||||
// Video codec types
|
||||
public static final int VCODEC_H264BP = 1;
|
||||
public static final int VCODEC_H264MP = 2;
|
||||
public static final int VCODEC_H263 = 3;
|
||||
public static final int VCODEC_MPEG4 = 4;
|
||||
|
||||
// The array of supported video codecs
|
||||
private static final int[] SUPPORTED_VCODECS = new int[] {
|
||||
VCODEC_H264BP,
|
||||
VCODEC_H263,
|
||||
VCODEC_MPEG4,
|
||||
};
|
||||
|
||||
// Audio codec types
|
||||
public static final int ACODEC_AAC_LC = 1;
|
||||
public static final int ACODEC_AMRNB = 2;
|
||||
public static final int ACODEC_AMRWB = 3;
|
||||
public static final int ACODEC_MP3 = 4;
|
||||
public static final int ACODEC_OGG = 5;
|
||||
|
||||
// The array of supported video codecs
|
||||
private static final int[] SUPPORTED_ACODECS = new int[] {
|
||||
ACODEC_AAC_LC,
|
||||
ACODEC_AMRNB,
|
||||
ACODEC_AMRWB
|
||||
};
|
||||
|
||||
// File format types
|
||||
public static final int FILE_UNSUPPORTED = 0;
|
||||
public static final int FILE_3GP = 1;
|
||||
public static final int FILE_MP4 = 2;
|
||||
public static final int FILE_JPEG = 3;
|
||||
public static final int FILE_PNG = 4;
|
||||
|
||||
// The array of the supported file formats
|
||||
private static final int[] SUPPORTED_VIDEO_FILE_FORMATS = new int[] {
|
||||
FILE_3GP,
|
||||
FILE_MP4
|
||||
};
|
||||
|
||||
// The maximum count of audio tracks supported
|
||||
public static final int AUDIO_MAX_TRACK_COUNT = 1;
|
||||
|
||||
// The maximum volume supported (100 means that no amplification is
|
||||
// supported, i.e. attenuation only)
|
||||
public static final int AUDIO_MAX_VOLUME_PERCENT = 100;
|
||||
|
||||
/**
|
||||
* This class cannot be instantiated
|
||||
*/
|
||||
private MediaProperties() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The array of supported aspect ratios
|
||||
*/
|
||||
public static int[] getAllSupportedAspectRatios() {
|
||||
return ASPECT_RATIOS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supported resolutions for the specified aspect ratio.
|
||||
*
|
||||
* @param aspectRatio The aspect ratio for which the resolutions are requested
|
||||
*
|
||||
* @return The array of width and height pairs
|
||||
*/
|
||||
public static Pair<Integer, Integer>[] getSupportedResolutions(int aspectRatio) {
|
||||
final Pair<Integer, Integer>[] resolutions;
|
||||
switch(aspectRatio) {
|
||||
case ASPECT_RATIO_3_2: {
|
||||
resolutions = ASPECT_RATIO_3_2_RESOLUTIONS;
|
||||
break;
|
||||
}
|
||||
|
||||
case ASPECT_RATIO_4_3: {
|
||||
resolutions = ASPECT_RATIO_4_3_RESOLUTIONS;
|
||||
break;
|
||||
}
|
||||
|
||||
case ASPECT_RATIO_5_3: {
|
||||
resolutions = ASPECT_RATIO_5_3_RESOLUTIONS;
|
||||
break;
|
||||
}
|
||||
|
||||
case ASPECT_RATIO_11_9: {
|
||||
resolutions = ASPECT_RATIO_11_9_RESOLUTIONS;
|
||||
break;
|
||||
}
|
||||
|
||||
case ASPECT_RATIO_16_9: {
|
||||
resolutions = ASPECT_RATIO_16_9_RESOLUTIONS;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new IllegalArgumentException("Unknown aspect ratio: " + aspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The array of supported video codecs
|
||||
*/
|
||||
public static int[] getSupportedVideoCodecs() {
|
||||
return SUPPORTED_VCODECS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The array of supported audio codecs
|
||||
*/
|
||||
public static int[] getSupportedAudioCodecs() {
|
||||
return SUPPORTED_ACODECS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The array of supported file formats
|
||||
*/
|
||||
public static int[] getSupportedVideoFileFormat() {
|
||||
return SUPPORTED_VIDEO_FILE_FORMATS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The array of supported video bitrates
|
||||
*/
|
||||
public static int[] getSupportedVideoBitrates() {
|
||||
return SUPPORTED_BITRATES;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The maximum value for the audio volume
|
||||
*/
|
||||
public static int getSupportedMaxVolume() {
|
||||
return MediaProperties.AUDIO_MAX_VOLUME_PERCENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The maximum number of audio tracks supported
|
||||
*/
|
||||
public static int getSupportedAudioTrackCount() {
|
||||
return MediaProperties.AUDIO_MAX_TRACK_COUNT;
|
||||
}
|
||||
}
|
||||
541
media/java/android/media/videoeditor/MediaVideoItem.java
Executable file
541
media/java/android/media/videoeditor/MediaVideoItem.java
Executable file
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaRecorder;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
/**
|
||||
* This class represents a video clip item on the storyboard
|
||||
* {@hide}
|
||||
*/
|
||||
public class MediaVideoItem extends MediaItem {
|
||||
// Logging
|
||||
private static final String TAG = "MediaVideoItem";
|
||||
|
||||
// Instance variables
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final int mAspectRatio;
|
||||
private final int mFileType;
|
||||
private final int mVideoType;
|
||||
private final int mVideoProfile;
|
||||
private final int mVideoBitrate;
|
||||
private final long mDurationMs;
|
||||
private final int mAudioBitrate;
|
||||
private final int mFps;
|
||||
private final int mAudioType;
|
||||
private final int mAudioChannels;
|
||||
private final int mAudioSamplingFrequency;
|
||||
|
||||
private long mBeginBoundaryTimeMs;
|
||||
private long mEndBoundaryTimeMs;
|
||||
private int mVolumePercentage;
|
||||
private String mAudioWaveformFilename;
|
||||
private PlaybackThread mPlaybackThread;
|
||||
|
||||
/**
|
||||
* This listener interface is used by the MediaVideoItem to emit playback
|
||||
* progress notifications. This callback should be invoked after the
|
||||
* number of frames specified by
|
||||
* {@link #startPlayback(SurfaceHolder surfaceHolder, long fromMs,
|
||||
* int callbackAfterFrameCount, PlaybackProgressListener listener)}
|
||||
*/
|
||||
public interface PlaybackProgressListener {
|
||||
/**
|
||||
* This method notifies the listener of the current time position while
|
||||
* playing a media item
|
||||
*
|
||||
* @param mediaItem The media item
|
||||
* @param timeMs The current playback position (expressed in milliseconds
|
||||
* since the beginning of the media item).
|
||||
* @param end true if the end of the media item was reached
|
||||
*/
|
||||
public void onProgress(MediaVideoItem mediaItem, long timeMs, boolean end);
|
||||
}
|
||||
|
||||
/**
|
||||
* The playback thread
|
||||
*/
|
||||
private class PlaybackThread extends Thread {
|
||||
// Instance variables
|
||||
private final static long FRAME_DURATION = 33;
|
||||
private final PlaybackProgressListener mListener;
|
||||
private final int mCallbackAfterFrameCount;
|
||||
private final long mFromMs, mToMs;
|
||||
private boolean mRun, mLoop;
|
||||
private long mPositionMs;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param fromMs The time (relative to the beginning of the media item)
|
||||
* at which the playback will start
|
||||
* @param toMs The time (relative to the beginning of the media item) at
|
||||
* which the playback will stop. Use -1 to play to the end of
|
||||
* the media item
|
||||
* @param loop true if the playback should be looped once it reaches the
|
||||
* end
|
||||
* @param callbackAfterFrameCount The listener interface should be
|
||||
* invoked after the number of frames specified by this
|
||||
* parameter.
|
||||
* @param listener The listener which will be notified of the playback
|
||||
* progress
|
||||
*/
|
||||
public PlaybackThread(long fromMs, long toMs, boolean loop, int callbackAfterFrameCount,
|
||||
PlaybackProgressListener listener) {
|
||||
mPositionMs = mFromMs = fromMs;
|
||||
if (toMs < 0) {
|
||||
mToMs = mDurationMs;
|
||||
} else {
|
||||
mToMs = toMs;
|
||||
}
|
||||
mLoop = loop;
|
||||
mCallbackAfterFrameCount = callbackAfterFrameCount;
|
||||
mListener = listener;
|
||||
mRun = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "===> PlaybackThread.run enter");
|
||||
}
|
||||
int frameCount = 0;
|
||||
while (mRun) {
|
||||
try {
|
||||
sleep(FRAME_DURATION);
|
||||
} catch (InterruptedException ex) {
|
||||
break;
|
||||
}
|
||||
frameCount++;
|
||||
mPositionMs += FRAME_DURATION;
|
||||
|
||||
if (mPositionMs >= mToMs) {
|
||||
if (!mLoop) {
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(MediaVideoItem.this, mPositionMs, true);
|
||||
}
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "PlaybackThread.run playback complete");
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// Fire a notification for the end of the clip
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(MediaVideoItem.this, mToMs, false);
|
||||
}
|
||||
|
||||
// Rewind
|
||||
mPositionMs = mFromMs;
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(MediaVideoItem.this, mPositionMs, false);
|
||||
}
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "PlaybackThread.run playback complete");
|
||||
}
|
||||
frameCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (frameCount == mCallbackAfterFrameCount) {
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(MediaVideoItem.this, mPositionMs, false);
|
||||
}
|
||||
frameCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "===> PlaybackThread.run exit");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the playback
|
||||
*
|
||||
* @return The stop position
|
||||
*/
|
||||
public long stopPlayback() {
|
||||
mRun = false;
|
||||
try {
|
||||
join();
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
return mPositionMs;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated with a default constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private MediaVideoItem() throws IOException {
|
||||
this(null, null, RENDERING_MODE_BLACK_BORDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param mediaItemId The MediaItem id
|
||||
* @param filename The image file name
|
||||
* @param renderingMode The rendering mode
|
||||
*
|
||||
* @throws IOException if the file cannot be opened for reading
|
||||
*/
|
||||
public MediaVideoItem(String mediaItemId, String filename, int renderingMode)
|
||||
throws IOException {
|
||||
this(mediaItemId, filename, renderingMode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param mediaItemId The MediaItem id
|
||||
* @param filename The image file name
|
||||
* @param renderingMode The rendering mode
|
||||
* @param audioWaveformFilename The name of the audio waveform file
|
||||
*
|
||||
* @throws IOException if the file cannot be opened for reading
|
||||
*/
|
||||
MediaVideoItem(String mediaItemId, String filename, int renderingMode,
|
||||
String audioWaveformFilename) throws IOException {
|
||||
super(mediaItemId, filename, renderingMode);
|
||||
// TODO: Set these variables correctly
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;
|
||||
mFileType = MediaProperties.FILE_MP4;
|
||||
mVideoType = MediaRecorder.VideoEncoder.H264;
|
||||
// Do we have predefined values for this variable?
|
||||
mVideoProfile = 0;
|
||||
// Can video and audio duration be different?
|
||||
mDurationMs = 10000;
|
||||
mVideoBitrate = 800000;
|
||||
mAudioBitrate = 30000;
|
||||
mFps = 30;
|
||||
mAudioType = MediaProperties.ACODEC_AAC_LC;
|
||||
mAudioChannels = 2;
|
||||
mAudioSamplingFrequency = 16000;
|
||||
|
||||
mBeginBoundaryTimeMs = 0;
|
||||
mEndBoundaryTimeMs = mDurationMs;
|
||||
mVolumePercentage = 100;
|
||||
mAudioWaveformFilename = audioWaveformFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the start and end marks for trimming a video media item
|
||||
*
|
||||
* @param beginMs Start time in milliseconds. Set to 0 to extract from the
|
||||
* beginning
|
||||
* @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to
|
||||
* extract until the end
|
||||
*
|
||||
* @throws IllegalArgumentException if the start time is greater or equal than
|
||||
* end time, the end time is beyond the file duration, the start time
|
||||
* is negative
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
mBeginBoundaryTimeMs = beginMs;
|
||||
mEndBoundaryTimeMs = endMs;
|
||||
// TODO: Validate/modify the start and the end time of effects and overlays
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The boundary begin time
|
||||
*/
|
||||
public long getBoundaryBeginTime() {
|
||||
return mBeginBoundaryTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The boundary end time
|
||||
*/
|
||||
public long getBoundaryEndTime() {
|
||||
return mEndBoundaryTimeMs;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void addEffect(Effect effect) {
|
||||
if (effect instanceof EffectKenBurns) {
|
||||
throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem");
|
||||
}
|
||||
super.addEffect(effect);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Bitmap getThumbnail(int width, int height, long timeMs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
|
||||
int thumbnailCount) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getAspectRatio() {
|
||||
return mAspectRatio;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getFileType() {
|
||||
return mFileType;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The timeline duration. This is the actual duration in the
|
||||
* timeline (trimmed duration)
|
||||
*/
|
||||
@Override
|
||||
public long getTimelineDuration() {
|
||||
return mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a frame according to the playback (in the native aspect ratio) for
|
||||
* the specified media item. All effects and overlays applied to the media
|
||||
* item are ignored. The extract boundaries are also ignored. This method
|
||||
* can be used to playback frames when implementing trimming functionality.
|
||||
*
|
||||
* @param surfaceHolder SurfaceHolder used by the application
|
||||
* @param timeMs time corresponding to the frame to display (relative to the
|
||||
* the beginning of the media item).
|
||||
* @return The accurate time stamp of the frame that is rendered .
|
||||
* @throws IllegalStateException if a playback, preview or an export is
|
||||
* already in progress
|
||||
* @throws IllegalArgumentException if time is negative or greater than the
|
||||
* media item duration
|
||||
*/
|
||||
public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) {
|
||||
return timeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the playback of this media item. This method does not block (does
|
||||
* not wait for the playback to complete). The PlaybackProgressListener
|
||||
* allows to track the progress at the time interval determined by the
|
||||
* callbackAfterFrameCount parameter. The SurfaceHolder has to be created
|
||||
* and ready for use before calling this method.
|
||||
*
|
||||
* @param surfaceHolder SurfaceHolder where the frames are rendered.
|
||||
* @param fromMs The time (relative to the beginning of the media item) at
|
||||
* which the playback will start
|
||||
* @param toMs The time (relative to the beginning of the media item) at
|
||||
* which the playback will stop. Use -1 to play to the end of the
|
||||
* media item
|
||||
* @param loop true if the playback should be looped once it reaches the end
|
||||
* @param callbackAfterFrameCount The listener interface should be invoked
|
||||
* after the number of frames specified by this parameter.
|
||||
* @param listener The listener which will be notified of the playback
|
||||
* progress
|
||||
* @throws IllegalArgumentException if fromMs or toMs is beyond the playback
|
||||
* duration
|
||||
* @throws IllegalStateException if a playback, preview or an export is
|
||||
* already in progress
|
||||
*/
|
||||
public void startPlayback(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop,
|
||||
int callbackAfterFrameCount, PlaybackProgressListener listener) {
|
||||
if (fromMs >= mDurationMs) {
|
||||
return;
|
||||
}
|
||||
mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, callbackAfterFrameCount,
|
||||
listener);
|
||||
mPlaybackThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the media item playback. This method blocks until the ongoing
|
||||
* playback is stopped.
|
||||
*
|
||||
* @return The accurate current time when stop is effective expressed in
|
||||
* milliseconds
|
||||
*/
|
||||
public long stopPlayback() {
|
||||
final long stopTimeMs;
|
||||
if (mPlaybackThread != null) {
|
||||
stopTimeMs = mPlaybackThread.stopPlayback();
|
||||
mPlaybackThread = null;
|
||||
} else {
|
||||
stopTimeMs = 0;
|
||||
}
|
||||
return stopTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* This API allows to generate a file containing the sample volume levels of
|
||||
* the Audio track of this media item. This function may take significant
|
||||
* time and is blocking. The file can be retrieved using
|
||||
* getAudioWaveformFilename().
|
||||
*
|
||||
* @param listener The progress listener
|
||||
*
|
||||
* @throws IOException if the output file cannot be created
|
||||
* @throws IllegalArgumentException if the mediaItem does not have a valid
|
||||
* Audio track
|
||||
*/
|
||||
public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
|
||||
throws IOException {
|
||||
// TODO: Set mAudioWaveformFilename at the end once the export is complete
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio waveform file name if {@link #extractAudioWaveform()} was
|
||||
* successful. The file format is as following:
|
||||
* <ul>
|
||||
* <li>first 4 bytes provide the number of samples for each value, as big-endian signed</li>
|
||||
* <li>4 following bytes is the total number of values in the file, as big-endian signed</li>
|
||||
* <li>all values follow as bytes Name is unique.</li>
|
||||
*</ul>
|
||||
* @return the name of the file, null if the file has not been computed or
|
||||
* if there is no Audio track in the mediaItem
|
||||
*/
|
||||
public String getAudioWaveformFilename() {
|
||||
return mAudioWaveformFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set volume of the Audio track of this mediaItem
|
||||
*
|
||||
* @param volumePercent in %/. 100% means no change; 50% means half value, 200%
|
||||
* means double, 0% means silent.
|
||||
* @throws UsupportedOperationException if volume value is not supported
|
||||
*/
|
||||
public void setVolume(int volumePercent) {
|
||||
mVolumePercentage = volumePercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the volume value of the audio track as percentage. Call of this
|
||||
* method before calling setVolume will always return 100%
|
||||
*
|
||||
* @return the volume in percentage
|
||||
*/
|
||||
public int getVolume() {
|
||||
return mVolumePercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The video type
|
||||
*/
|
||||
public int getVideoType() {
|
||||
return mVideoType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The video profile
|
||||
*/
|
||||
public int getVideoProfile() {
|
||||
return mVideoProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The video bitrate
|
||||
*/
|
||||
public int getVideoBitrate() {
|
||||
return mVideoBitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The audio bitrate
|
||||
*/
|
||||
public int getAudioBitrate() {
|
||||
return mAudioBitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of frames per second
|
||||
*/
|
||||
public int getFps() {
|
||||
return mFps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The audio codec
|
||||
*/
|
||||
public int getAudioType() {
|
||||
return mAudioType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of audio channels
|
||||
*/
|
||||
public int getAudioChannels() {
|
||||
return mAudioChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The audio sample frequency
|
||||
*/
|
||||
public int getAudioSamplingFrequency() {
|
||||
return mAudioSamplingFrequency;
|
||||
}
|
||||
}
|
||||
117
media/java/android/media/videoeditor/Overlay.java
Executable file
117
media/java/android/media/videoeditor/Overlay.java
Executable file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* This is the super class for all Overlay classes.
|
||||
* {@hide}
|
||||
*/
|
||||
public abstract class Overlay {
|
||||
// Instance variables
|
||||
private final String mUniqueId;
|
||||
|
||||
protected long mStartTimeMs;
|
||||
protected long mDurationMs;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private Overlay() {
|
||||
mUniqueId = null;
|
||||
mStartTimeMs = 0;
|
||||
mDurationMs = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param overlayId The overlay id
|
||||
* @param startTimeMs The start time relative to the media item start time
|
||||
* @param durationMs The duration
|
||||
*
|
||||
* @throws IllegalArgumentException if the file type is not PNG or the
|
||||
* startTimeMs and durationMs are incorrect.
|
||||
*/
|
||||
public Overlay(String overlayId, long startTimeMs, long durationMs) {
|
||||
mUniqueId = overlayId;
|
||||
mStartTimeMs = startTimeMs;
|
||||
mDurationMs = durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The of the overlay
|
||||
*/
|
||||
public String getId() {
|
||||
return mUniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The duration of the overlay effect
|
||||
*/
|
||||
public long getDuration() {
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a preview or export is in progress, then this change is effective for
|
||||
* next preview or export session.
|
||||
*
|
||||
* @param durationMs The duration in milliseconds
|
||||
*/
|
||||
public void setDuration(long durationMs) {
|
||||
mDurationMs = durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the start time of the overlay
|
||||
*/
|
||||
public long getStartTime() {
|
||||
return mStartTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start time for the overlay. If a preview or export is in
|
||||
* progress, then this change is effective for next preview or export
|
||||
* session.
|
||||
*
|
||||
* @param startTimeMs start time in milliseconds
|
||||
*/
|
||||
public void setStartTime(long startTimeMs) {
|
||||
mStartTimeMs = startTimeMs;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof Overlay)) {
|
||||
return false;
|
||||
}
|
||||
return mUniqueId.equals(((Overlay)object).mUniqueId);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mUniqueId.hashCode();
|
||||
}
|
||||
}
|
||||
62
media/java/android/media/videoeditor/OverlayFrame.java
Executable file
62
media/java/android/media/videoeditor/OverlayFrame.java
Executable file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* This class is used to overlay an image on top of a media item. This class
|
||||
* does not manage deletion of the overlay file so application may use
|
||||
* {@link #getFilename()} for this purpose.
|
||||
* {@hide}
|
||||
*/
|
||||
public class OverlayFrame extends Overlay {
|
||||
// Instance variables
|
||||
private final String mFilename;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private OverlayFrame() {
|
||||
this(null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for an OverlayFrame
|
||||
*
|
||||
* @param overlayId The overlay id
|
||||
* @param filename The file name that contains the overlay. Only PNG
|
||||
* supported.
|
||||
* @param startTimeMs The overlay start time in milliseconds
|
||||
* @param durationMs The overlay duration in milliseconds
|
||||
*
|
||||
* @throws IllegalArgumentException if the file type is not PNG or the
|
||||
* startTimeMs and durationMs are incorrect.
|
||||
*/
|
||||
public OverlayFrame(String overlayId, String filename, long startTimeMs, long durationMs) {
|
||||
super(overlayId, startTimeMs, durationMs);
|
||||
mFilename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file name of this overlay
|
||||
*/
|
||||
public String getFilename() {
|
||||
return mFilename;
|
||||
}
|
||||
}
|
||||
182
media/java/android/media/videoeditor/Transition.java
Executable file
182
media/java/android/media/videoeditor/Transition.java
Executable file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This class is super class for all transitions. Transitions (with the
|
||||
* exception of TransitionAtStart and TransitioAtEnd) can only be inserted
|
||||
* between media items.
|
||||
*
|
||||
* Adding a transition between MediaItems makes the
|
||||
* duration of the storyboard shorter by the duration of the Transition itself.
|
||||
* As a result, if the duration of the transition is larger than the smaller
|
||||
* duration of the two MediaItems associated with the Transition, an exception
|
||||
* will be thrown.
|
||||
*
|
||||
* During a transition, the audio track are cross-fading
|
||||
* automatically. {@hide}
|
||||
*/
|
||||
public abstract class Transition {
|
||||
// The transition behavior
|
||||
/** The transition starts slowly and speed up */
|
||||
public static final int BEHAVIOR_SPEED_UP = 0;
|
||||
/** The transition start fast and speed down */
|
||||
public static final int BEHAVIOR_SPEED_DOWN = 1;
|
||||
/** The transition speed is constant */
|
||||
public static final int BEHAVIOR_LINEAR = 2;
|
||||
/** The transition starts fast and ends fast with a slow middle */
|
||||
public static final int BEHAVIOR_MIDDLE_SLOW = 3;
|
||||
/** The transition starts slowly and ends slowly with a fast middle */
|
||||
public static final int BEHAVIOR_MIDDLE_FAST = 4;
|
||||
|
||||
// The unique id of the transition
|
||||
private final String mUniqueId;
|
||||
|
||||
// The transition is applied at the end of this media item
|
||||
private final MediaItem mAfterMediaItem;
|
||||
// The transition is applied at the beginning of this media item
|
||||
private final MediaItem mBeforeMediaItem;
|
||||
|
||||
// The transition behavior
|
||||
protected final int mBehavior;
|
||||
|
||||
// The transition duration
|
||||
protected long mDurationMs;
|
||||
|
||||
// The transition filename
|
||||
protected String mFilename;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private Transition() {
|
||||
this(null, null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
* @param afterMediaItem The transition is applied to the end of this
|
||||
* media item
|
||||
* @param beforeMediaItem The transition is applied to the beginning of
|
||||
* this media item
|
||||
* @param durationMs The duration of the transition in milliseconds
|
||||
* @param behavior The transition behavior
|
||||
*/
|
||||
protected Transition(String transitionId, MediaItem afterMediaItem, MediaItem beforeMediaItem,
|
||||
long durationMs, int behavior) {
|
||||
mUniqueId = transitionId;
|
||||
mAfterMediaItem = afterMediaItem;
|
||||
mBeforeMediaItem = beforeMediaItem;
|
||||
mDurationMs = durationMs;
|
||||
mBehavior = behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The of the transition
|
||||
*/
|
||||
public String getId() {
|
||||
return mUniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The media item at the end of which the transition is applied
|
||||
*/
|
||||
public MediaItem getAfterMediaItem() {
|
||||
return mAfterMediaItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The media item at the beginning of which the transition is applied
|
||||
*/
|
||||
public MediaItem getBeforeMediaItem() {
|
||||
return mBeforeMediaItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the duration of the transition.
|
||||
*
|
||||
* @param durationMs the duration of the transition in milliseconds
|
||||
*/
|
||||
public void setDuration(long durationMs) {
|
||||
mDurationMs = durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the duration of the transition in milliseconds
|
||||
*/
|
||||
public long getDuration() {
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The behavior
|
||||
*/
|
||||
public int getBehavior() {
|
||||
return mBehavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the video clip for the specified transition.
|
||||
* This method may block for a significant amount of time.
|
||||
*
|
||||
* Before the method completes execution it sets the mFilename to
|
||||
* the name of the newly generated transition video clip file.
|
||||
*/
|
||||
abstract void generate();
|
||||
|
||||
/**
|
||||
* Remove any resources associated with this transition
|
||||
*/
|
||||
void invalidate() {
|
||||
if (mFilename != null) {
|
||||
new File(mFilename).delete();
|
||||
mFilename = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the transition is generated
|
||||
*/
|
||||
boolean isGenerated() {
|
||||
return (mFilename != null);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof Transition)) {
|
||||
return false;
|
||||
}
|
||||
return mUniqueId.equals(((Transition)object).mUniqueId);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mUniqueId.hashCode();
|
||||
}
|
||||
}
|
||||
112
media/java/android/media/videoeditor/TransitionAlpha.java
Executable file
112
media/java/android/media/videoeditor/TransitionAlpha.java
Executable file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* This class allows to render an "alpha blending" transition according to a
|
||||
* bitmap mask. The mask shows the shape of the transition all along the
|
||||
* duration of the transition: just before the transition, video 1 is fully
|
||||
* displayed. When the transition starts, as the time goes on, pixels of video 2
|
||||
* replace pixels of video 1 according to the gray scale pixel value of the
|
||||
* mask.
|
||||
* {@hide}
|
||||
*/
|
||||
public class TransitionAlpha extends Transition {
|
||||
/** This is the input JPEG file for the mask */
|
||||
private final String mMaskFilename;
|
||||
|
||||
/**
|
||||
* This is percentage (between 0 and 100) of blending between video 1 and
|
||||
* video 2 if this value equals 0, then the mask is strictly applied if this
|
||||
* value equals 100, then the mask is not at all applied (no transition
|
||||
* effect)
|
||||
*/
|
||||
private final int mBlendingPercent;
|
||||
|
||||
/**
|
||||
* If true, this value inverts the direction of the mask: white pixels of
|
||||
* the mask show video 2 pixels first black pixels of the mask show video 2
|
||||
* pixels last.
|
||||
*/
|
||||
private final boolean mIsInvert;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private TransitionAlpha() {
|
||||
this(null, null, null, 0, 0, null, 0, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
* @param afterMediaItem The transition is applied to the end of this
|
||||
* media item
|
||||
* @param beforeMediaItem The transition is applied to the beginning of
|
||||
* this media item
|
||||
* @param durationMs duration of the transition in milliseconds
|
||||
* @param behavior behavior is one of the behavior defined in Transition
|
||||
* class
|
||||
* @param maskFilename JPEG file name
|
||||
* @param blendingPercent The blending percent applied
|
||||
* @param invert true to invert the direction of the alpha blending
|
||||
*
|
||||
* @throws IllegalArgumentException if behavior is not supported, or if
|
||||
* direction are not supported.
|
||||
*/
|
||||
public TransitionAlpha(String transitionId, MediaItem afterMediaItem,
|
||||
MediaItem beforeMediaItem, long durationMs, int behavior, String maskFilename,
|
||||
int blendingPercent, boolean invert) {
|
||||
super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);
|
||||
|
||||
mMaskFilename = maskFilename;
|
||||
mBlendingPercent = blendingPercent;
|
||||
mIsInvert = invert;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The blending percentage
|
||||
*/
|
||||
public int getBlendingPercent() {
|
||||
return mBlendingPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The mask filename
|
||||
*/
|
||||
public String getMaskFilename() {
|
||||
return mMaskFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the direction of the alpha blending is inverted
|
||||
*/
|
||||
public boolean isInvert() {
|
||||
return mIsInvert;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void generate() {
|
||||
}
|
||||
}
|
||||
81
media/java/android/media/videoeditor/TransitionAtEnd.java
Executable file
81
media/java/android/media/videoeditor/TransitionAtEnd.java
Executable file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* TransitionAtEnd is a class useful to manage a predefined transition at the
|
||||
* end of the movie.
|
||||
* {@hide}
|
||||
*/
|
||||
public class TransitionAtEnd extends Transition {
|
||||
/**
|
||||
* This transition fades to black frame using fade out in a certain provided
|
||||
* duration. This transition is always applied at the end of the movie.
|
||||
*/
|
||||
public static final int TYPE_FADE_TO_BLACK = 0;
|
||||
|
||||
/**
|
||||
* This transition fades to black frame using curtain closing: A black image is
|
||||
* moved from top to bottom to cover the video. This transition is always
|
||||
* applied at the end of the movie.
|
||||
*/
|
||||
public static final int TYPE_CURTAIN_CLOSING = 1;
|
||||
|
||||
// The transition type
|
||||
private final int mType;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private TransitionAtEnd() {
|
||||
this(null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
* @param afterMediaItem The transition is applied to the end of this
|
||||
* media item
|
||||
* @param durationMs duration of the transition in milliseconds
|
||||
* @param type type of the transition to apply.
|
||||
*/
|
||||
public TransitionAtEnd(String transitionId, MediaItem afterMediaItem, long duration,
|
||||
int type) {
|
||||
super(transitionId, afterMediaItem, null, duration, Transition.BEHAVIOR_LINEAR);
|
||||
mType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this transition
|
||||
*
|
||||
* @return The type of the transition
|
||||
*/
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void generate() {
|
||||
}
|
||||
}
|
||||
90
media/java/android/media/videoeditor/TransitionAtStart.java
Executable file
90
media/java/android/media/videoeditor/TransitionAtStart.java
Executable file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* TransitionAtStart is a class useful to manage a predefined transition at the
|
||||
* beginning of the movie.
|
||||
* {@hide}
|
||||
*/
|
||||
public class TransitionAtStart extends Transition {
|
||||
/**
|
||||
* This transition fades from black using fade-in in a certain provided
|
||||
* duration. This transition is always applied at the beginning of the
|
||||
* movie.
|
||||
*/
|
||||
public static final int TYPE_FADE_FROM_BLACK = 0;
|
||||
|
||||
/**
|
||||
* This transition fades from black frame using curtain opening: A black
|
||||
* image is displayed and moves from bottom to top making the video visible.
|
||||
* This transition is always applied at the beginning of the movie.
|
||||
*/
|
||||
public static final int TYPE_CURTAIN_OPENING = 1;
|
||||
|
||||
// The transition type
|
||||
private final int mType;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private TransitionAtStart() {
|
||||
this(null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
* @param beforeMediaItem The transition is applied to the beginning of
|
||||
* this media item
|
||||
* @param durationMs The duration of the transition in milliseconds
|
||||
* @param type The type of the transition to apply.
|
||||
*/
|
||||
public TransitionAtStart(String transitionId, MediaItem beforeMediaItem, long durationMs,
|
||||
int type) {
|
||||
super(transitionId, null, beforeMediaItem, durationMs,
|
||||
Transition.BEHAVIOR_LINEAR);
|
||||
mType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this transition
|
||||
*
|
||||
* @return The type of the transition
|
||||
*/
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void generate() {
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void invalidate() {
|
||||
}
|
||||
}
|
||||
60
media/java/android/media/videoeditor/TransitionCrossfade.java
Executable file
60
media/java/android/media/videoeditor/TransitionCrossfade.java
Executable file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* This class allows to render a crossfade (dissolve) effect transition between
|
||||
* two videos
|
||||
* {@hide}
|
||||
*/
|
||||
public class TransitionCrossfade extends Transition {
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private TransitionCrossfade() {
|
||||
this(null, null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
* @param afterMediaItem The transition is applied to the end of this
|
||||
* media item
|
||||
* @param beforeMediaItem The transition is applied to the beginning of
|
||||
* this media item
|
||||
* @param durationMs duration of the transition in milliseconds
|
||||
* @param behavior behavior is one of the behavior defined in Transition
|
||||
* class
|
||||
*
|
||||
* @throws IllegalArgumentException if behavior is not supported.
|
||||
*/
|
||||
public TransitionCrossfade(String transitionId, MediaItem afterMediaItem,
|
||||
MediaItem beforeMediaItem, long durationMs, int behavior) {
|
||||
super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void generate() {
|
||||
}
|
||||
}
|
||||
59
media/java/android/media/videoeditor/TransitionFadeToBlack.java
Executable file
59
media/java/android/media/videoeditor/TransitionFadeToBlack.java
Executable file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* This class is used to render a fade to black transition between two videos.
|
||||
* {@hide}
|
||||
*/
|
||||
public class TransitionFadeToBlack extends Transition {
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private TransitionFadeToBlack() {
|
||||
this(null, null, null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
* @param afterMediaItem The transition is applied to the end of this
|
||||
* media item
|
||||
* @param beforeMediaItem The transition is applied to the beginning of
|
||||
* this media item
|
||||
* @param durationMs duration of the transition
|
||||
* @param behavior behavior is one of the behavior defined in Transition
|
||||
* class
|
||||
*
|
||||
* @throws IllegalArgumentException if behavior is not supported.
|
||||
*/
|
||||
public TransitionFadeToBlack(String transitionId, MediaItem afterMediaItem,
|
||||
MediaItem beforeMediaItem, long durationMs, int behavior) {
|
||||
super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void generate() {
|
||||
}
|
||||
}
|
||||
82
media/java/android/media/videoeditor/TransitionSliding.java
Executable file
82
media/java/android/media/videoeditor/TransitionSliding.java
Executable file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This class allows to create sliding transitions
|
||||
* {@hide}
|
||||
*/
|
||||
public class TransitionSliding extends Transition {
|
||||
|
||||
/** Video 1 is pushed to the right while video 2 is coming from left */
|
||||
public final static int DIRECTION_RIGHT_OUT_LEFT_IN = 0;
|
||||
/** Video 1 is pushed to the left while video 2 is coming from right */
|
||||
public static final int DIRECTION_LEFT_OUT_RIGHT_IN = 1;
|
||||
/** Video 1 is pushed to the top while video 2 is coming from bottom */
|
||||
public static final int DIRECTION_TOP_OUT_BOTTOM_IN = 2;
|
||||
/** Video 1 is pushed to the bottom while video 2 is coming from top */
|
||||
public static final int DIRECTION_BOTTOM_OUT_TOP_IN = 3;
|
||||
|
||||
// The sliding transitions
|
||||
private final int mSlidingDirection;
|
||||
|
||||
/**
|
||||
* An object of this type cannot be instantiated by using the default
|
||||
* constructor
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private TransitionSliding() {
|
||||
this(null, null, null, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
* @param afterMediaItem The transition is applied to the end of this
|
||||
* media item
|
||||
* @param beforeMediaItem The transition is applied to the beginning of
|
||||
* this media item
|
||||
* @param durationMs duration of the transition in milliseconds
|
||||
* @param behavior behavior is one of the behavior defined in Transition
|
||||
* class
|
||||
* @param direction direction shall be one of the supported directions like
|
||||
* RIGHT_OUT_LEFT_IN
|
||||
*
|
||||
* @throws IllegalArgumentException if behavior is not supported.
|
||||
*/
|
||||
public TransitionSliding(String transitionId, MediaItem afterMediaItem,
|
||||
MediaItem beforeMediaItem, long durationMs, int behavior, int direction) {
|
||||
super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior);
|
||||
mSlidingDirection = direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The sliding direction
|
||||
*/
|
||||
public int getDirection() {
|
||||
return mSlidingDirection;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
void generate() {
|
||||
}
|
||||
}
|
||||
493
media/java/android/media/videoeditor/VideoEditor.java
Executable file
493
media/java/android/media/videoeditor/VideoEditor.java
Executable file
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
/**
|
||||
* This is the interface implemented by classes which provide video editing
|
||||
* functionality. The VideoEditor implementation class manages all input and
|
||||
* output files. Unless specifically mentioned, methods are blocking. A
|
||||
* typical editing session may consist of the following sequence of operations:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Add a set of MediaItems</li>
|
||||
* <li>Apply a set of Transitions between MediaItems</li>
|
||||
* <li>Add Effects and Overlays to media items</li>
|
||||
* <li>Preview the movie at any time</li>
|
||||
* <li>Save the VideoEditor implementation class internal state</li>
|
||||
* <li>Release the VideoEditor implementation class instance by invoking
|
||||
* {@link #release()}
|
||||
* </ul>
|
||||
* The internal VideoEditor state consists of the following elements:
|
||||
* <ul>
|
||||
* <li>Ordered & trimmed MediaItems</li>
|
||||
* <li>Transition video clips</li>
|
||||
* <li>Overlays</li>
|
||||
* <li>Effects</li>
|
||||
* <li>Audio waveform for the background audio and MediaItems</li>
|
||||
* <li>Project thumbnail</li>
|
||||
* <li>Last exported movie.</li>
|
||||
* <li>Other project specific data such as the current aspect ratio.</li>
|
||||
* </ul>
|
||||
* {@hide}
|
||||
*/
|
||||
public interface VideoEditor {
|
||||
// The file name of the project thumbnail
|
||||
public static final String THUMBNAIL_FILENAME = "thumbnail.jpg";
|
||||
|
||||
// Use this value instead of the specific end of the storyboard timeline
|
||||
// value.
|
||||
public final static int DURATION_OF_STORYBOARD = -1;
|
||||
|
||||
/**
|
||||
* This listener interface is used by the VideoEditor to emit preview
|
||||
* progress notifications. This callback should be invoked after the
|
||||
* number of frames specified by
|
||||
* {@link #startPreview(SurfaceHolder surfaceHolder, long fromMs,
|
||||
* int callbackAfterFrameCount, PreviewProgressListener listener)}
|
||||
*/
|
||||
public interface PreviewProgressListener {
|
||||
/**
|
||||
* This method notifies the listener of the current time position while
|
||||
* previewing a project.
|
||||
*
|
||||
* @param videoEditor The VideoEditor instance
|
||||
* @param timeMs The current preview position (expressed in milliseconds
|
||||
* since the beginning of the storyboard timeline).
|
||||
* @param end true if the end of the timeline was reached
|
||||
*/
|
||||
public void onProgress(VideoEditor videoEditor, long timeMs, boolean end);
|
||||
}
|
||||
|
||||
/**
|
||||
* This listener interface is used by the VideoEditor to emit export status
|
||||
* notifications.
|
||||
* {@link #export(String filename, ExportProgressListener listener, int height, int bitrate)}
|
||||
*/
|
||||
public interface ExportProgressListener {
|
||||
/**
|
||||
* This method notifies the listener of the progress status of a export
|
||||
* operation.
|
||||
*
|
||||
* @param videoEditor The VideoEditor instance
|
||||
* @param filename The name of the file which is in the process of being
|
||||
* exported.
|
||||
* @param progress The progress in %. At the beginning of the export, this
|
||||
* value is set to 0; at the end, the value is set to 100.
|
||||
*/
|
||||
public void onProgress(VideoEditor videoEditor, String filename, int progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The path where the VideoEditor stores all files related to the
|
||||
* project
|
||||
*/
|
||||
public String getPath();
|
||||
|
||||
/**
|
||||
* This method releases all in-memory resources used by the VideoEditor
|
||||
* instance. All pending operations such as preview, export and extract
|
||||
* audio waveform must be canceled.
|
||||
*/
|
||||
public void release();
|
||||
|
||||
/**
|
||||
* Persist the current internal state of VideoEditor to the project path.
|
||||
* The VideoEditor state may be restored by invoking the
|
||||
* {@link VideoEditorFactory#load(String)} method. This method does not
|
||||
* release the internal in-memory state of the VideoEditor. To release
|
||||
* the in-memory state of the VideoEditor the {@link #release()} method
|
||||
* must be invoked.
|
||||
*
|
||||
* Pending transition generations must be allowed to complete before the
|
||||
* state is saved.
|
||||
* Pending audio waveform generations must be allowed to complete.
|
||||
* Pending export operations must be allowed to continue.
|
||||
*/
|
||||
public void save() throws IOException;
|
||||
|
||||
/**
|
||||
* Create the output movie based on all media items added and the applied
|
||||
* storyboard items. This method can take a long time to execute and is
|
||||
* blocking. The application will receive progress notifications via the
|
||||
* ExportProgressListener. Specific implementations may not support multiple
|
||||
* simultaneous export operations.
|
||||
*
|
||||
* Note that invoking methods which would change the contents of the output
|
||||
* movie throw an IllegalStateException while an export operation is
|
||||
* pending.
|
||||
*
|
||||
* @param filename The output file name (including the full path)
|
||||
* @param height The height of the output video file. The supported values
|
||||
* for height are described in the MediaProperties class, for
|
||||
* example: HEIGHT_480. The width will be automatically
|
||||
* computed according to the aspect ratio provided by
|
||||
* {@link #setAspectRatio(int)}
|
||||
* @param bitrate The bitrate of the output video file. This is approximate
|
||||
* value for the output movie. Supported bitrate values are
|
||||
* described in the MediaProperties class for example:
|
||||
* BITRATE_384K
|
||||
* @param listener The listener for progress notifications. Use null if
|
||||
* export progress notifications are not needed.
|
||||
*
|
||||
* @throws IllegalArgumentException if height or bitrate are not supported.
|
||||
* @throws IOException if output file cannot be created
|
||||
* @throws IllegalStateException if a preview or an export is in progress or
|
||||
* if no MediaItem has been added
|
||||
* @throws CancellationException if export is canceled by calling
|
||||
* {@link #cancelExport()}
|
||||
* @throws UnsupportOperationException if multiple simultaneous export()
|
||||
* are not allowed
|
||||
*/
|
||||
public void export(String filename, int height, int bitrate, ExportProgressListener listener)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Cancel the running export operation. This method blocks until the
|
||||
* export is canceled and the exported file (if any) is deleted. If the
|
||||
* export completed by the time this method is invoked, the export file
|
||||
* will be deleted.
|
||||
*
|
||||
* @param filename The filename which identifies the export operation to be
|
||||
* canceled.
|
||||
**/
|
||||
public void cancelExport(String filename);
|
||||
|
||||
/**
|
||||
* Add a media item at the end of the storyboard.
|
||||
*
|
||||
* @param mediaItem The media item object to add
|
||||
* @throws IllegalStateException if a preview or an export is in progress or
|
||||
* if the media item id is not unique across all the media items
|
||||
* added.
|
||||
*/
|
||||
public void addMediaItem(MediaItem mediaItem);
|
||||
|
||||
/**
|
||||
* Insert a media item after the media item with the specified id.
|
||||
*
|
||||
* @param mediaItem The media item object to insert
|
||||
* @param afterMediaItemId Insert the mediaItem after the media item
|
||||
* identified by this id. If this parameter is null, the media
|
||||
* item is inserted at the beginning of the timeline.
|
||||
*
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if media item with the specified id does
|
||||
* not exist (null is a valid value) or if the media item id is
|
||||
* not unique across all the media items added.
|
||||
*/
|
||||
public void insertMediaItem(MediaItem mediaItem, String afterMediaItemId);
|
||||
|
||||
/**
|
||||
* Move a media item after the media item with the specified id.
|
||||
*
|
||||
* Note: The project thumbnail is regenerated if the media item is or
|
||||
* becomes the first media item in the storyboard timeline.
|
||||
*
|
||||
* @param mediaItemId The id of the media item to move
|
||||
* @param afterMediaItemId Move the media item identified by mediaItemId after
|
||||
* the media item identified by this parameter. If this parameter
|
||||
* is null, the media item is moved at the beginning of the
|
||||
* timeline.
|
||||
*
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if one of media item ids is invalid
|
||||
* (null is a valid value)
|
||||
*/
|
||||
public void moveMediaItem(String mediaItemId, String afterMediaItemId);
|
||||
|
||||
/**
|
||||
* Remove the media item with the specified id. If there are transitions
|
||||
* before or after this media item, then this/these transition(s) are
|
||||
* removed from the storyboard. If the extraction of the audio waveform is
|
||||
* in progress, the extraction is canceled and the file is deleted.
|
||||
*
|
||||
* Effects and overlays associated with the media item will also be
|
||||
* removed.
|
||||
*
|
||||
* Note: The project thumbnail is regenerated if the media item which
|
||||
* is removed is the first media item in the storyboard or if the
|
||||
* media item is the only one in the storyboard. If the
|
||||
* media item is the only one in the storyboard, the project thumbnail
|
||||
* will be set to a black frame and the aspect ratio will revert to the
|
||||
* default aspect ratio, and this method is equivalent to
|
||||
* removeAllMediaItems() in this case.
|
||||
*
|
||||
* @param mediaItemId The unique id of the media item to be removed
|
||||
*
|
||||
* @return The media item that was removed
|
||||
*
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if media item with the specified id
|
||||
* does not exist
|
||||
*/
|
||||
public MediaItem removeMediaItem(String mediaItemId);
|
||||
|
||||
/**
|
||||
* Remove all media items in the storyboard. All effects, overlays and all
|
||||
* transitions are also removed.
|
||||
*
|
||||
* Note: The project thumbnail will be set to a black frame and the aspect
|
||||
* ratio will revert to the default aspect ratio.
|
||||
*
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
*/
|
||||
public void removeAllMediaItems();
|
||||
|
||||
/**
|
||||
* Get the list of media items in the order in which it they appear in the
|
||||
* storyboard timeline.
|
||||
*
|
||||
* Note that if any media item source files are no longer
|
||||
* accessible, this method will still provide the full list of media items.
|
||||
*
|
||||
* @return The list of media items. If no media item exist an empty list
|
||||
* will be returned.
|
||||
*/
|
||||
public List<MediaItem> getAllMediaItems();
|
||||
|
||||
/**
|
||||
* Find the media item with the specified id
|
||||
*
|
||||
* @param mediaItemId The media item id
|
||||
*
|
||||
* @return The media item with the specified id (null if it does not exist)
|
||||
*/
|
||||
public MediaItem getMediaItem(String mediaItemId);
|
||||
|
||||
/**
|
||||
* Add a transition between the media items specified by the transition.
|
||||
* If a transition existed at the same position it is invalidated and then
|
||||
* the transition is replaced. Note that the new transition video clip is
|
||||
* not automatically generated by this method. The
|
||||
* {@link Transition#generate()} method must be invoked to generate
|
||||
* the transition video clip.
|
||||
*
|
||||
* Note that the TransitionAtEnd and TransitionAtStart are special kinds
|
||||
* that can not be applied between two media items.
|
||||
*
|
||||
* A crossfade audio transition will be automatically applied regardless of
|
||||
* the video transition.
|
||||
*
|
||||
* @param transition The transition to apply
|
||||
*
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if the transition duration is larger
|
||||
* than the smallest duration of the two media item files or
|
||||
* if the two media items specified in the transition are not
|
||||
* adjacent
|
||||
*/
|
||||
public void addTransition(Transition transition);
|
||||
|
||||
/**
|
||||
* Remove the transition with the specified id.
|
||||
*
|
||||
* @param transitionId The id of the transition to be removed
|
||||
*
|
||||
* @return The transition that was removed
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if transition with the specified id does
|
||||
* not exist
|
||||
*/
|
||||
public Transition removeTransition(String transitionId);
|
||||
|
||||
/**
|
||||
* Get the list of transitions
|
||||
*
|
||||
* @return The list of transitions. If no transitions exist an empty list
|
||||
* will be returned.
|
||||
*/
|
||||
public List<Transition> getAllTransitions();
|
||||
|
||||
/**
|
||||
* Find the transition with the specified transition id.
|
||||
*
|
||||
* @param transitionId The transition id
|
||||
*
|
||||
* @return The transition
|
||||
*/
|
||||
public Transition getTransition(String transitionId);
|
||||
|
||||
/**
|
||||
* Add the specified AudioTrack to the storyboard. Note: Specific
|
||||
* implementations may support a limited number of audio tracks (e.g. only
|
||||
* one audio track)
|
||||
*
|
||||
* @param audioTrack The AudioTrack to add
|
||||
* @throws UnsupportedOperationException if the implementation supports a
|
||||
* limited number of audio tracks.
|
||||
* @throws IllegalArgumentException if media item is not unique across all
|
||||
* the audio tracks already added.
|
||||
*/
|
||||
public void addAudioTrack(AudioTrack audioTrack);
|
||||
|
||||
/**
|
||||
* Insert an audio track after the audio track with the specified id. Use
|
||||
* addAudioTrack to add an audio track at the end of the storyboard
|
||||
* timeline.
|
||||
*
|
||||
* @param audioTrack The audio track object to insert
|
||||
* @param afterAudioTrackId Insert the audio track after the audio track
|
||||
* identified by this parameter. If this parameter is null the
|
||||
* audio track is added at the beginning of the timeline.
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if media item with the specified id does
|
||||
* not exist (null is a valid value). if media item is not
|
||||
* unique across all the audio tracks already added.
|
||||
* @throws UnsupportedOperationException if the implementation supports a
|
||||
* limited number of audio tracks
|
||||
*/
|
||||
public void insertAudioTrack(AudioTrack audioTrack, String afterAudioTrackId);
|
||||
|
||||
/**
|
||||
* Move an AudioTrack after the AudioTrack with the specified id.
|
||||
*
|
||||
* @param audioTrackId The id of the AudioTrack to move
|
||||
* @param afterAudioTrackId Move the AudioTrack identified by audioTrackId
|
||||
* after the AudioTrack identified by this parameter. If this
|
||||
* parameter is null the audio track is added at the beginning of
|
||||
* the timeline.
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if one of media item ids is invalid
|
||||
* (null is a valid value)
|
||||
*/
|
||||
public void moveAudioTrack(String audioTrackId, String afterAudioTrackId);
|
||||
|
||||
/**
|
||||
* Remove the audio track with the specified id. If the extraction of the
|
||||
* audio waveform is in progress, the extraction is canceled and the file is
|
||||
* deleted.
|
||||
*
|
||||
* @param audioTrackId The id of the audio track to be removed
|
||||
*
|
||||
* @return The audio track that was removed
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
*/
|
||||
public AudioTrack removeAudioTrack(String audioTrackId);
|
||||
|
||||
/**
|
||||
* Get the list of AudioTracks in order in which they appear in the storyboard.
|
||||
*
|
||||
* Note that if any AudioTrack source files are not accessible anymore,
|
||||
* this method will still provide the full list of audio tracks.
|
||||
*
|
||||
* @return The list of AudioTracks. If no audio tracks exist an empty list
|
||||
* will be returned.
|
||||
*/
|
||||
public List<AudioTrack> getAllAudioTracks();
|
||||
|
||||
/**
|
||||
* Find the AudioTrack with the specified id
|
||||
*
|
||||
* @param audioTrackId The AudioTrack id
|
||||
*
|
||||
* @return The AudioTrack with the specified id (null if it does not exist)
|
||||
*/
|
||||
public AudioTrack getAudioTrack(String audioTrackId);
|
||||
|
||||
/**
|
||||
* Set the aspect ratio used in the preview and the export movie.
|
||||
*
|
||||
* The default aspect ratio is ASPECTRATIO_16_9 (16:9).
|
||||
*
|
||||
* @param aspectRatio to apply. If aspectRatio is the same as the current
|
||||
* aspect ratio, then this function just returns. The supported
|
||||
* aspect ratio are defined in the MediaProperties class for
|
||||
* example: ASPECTRATIO_16_9
|
||||
*
|
||||
* @throws IllegalStateException if a preview or an export is in progress
|
||||
* @throws IllegalArgumentException if aspect ratio is not supported
|
||||
*/
|
||||
public void setAspectRatio(int aspectRatio);
|
||||
|
||||
/**
|
||||
* Get current aspect ratio.
|
||||
*
|
||||
* @return The aspect ratio as described in MediaProperties
|
||||
*/
|
||||
public int getAspectRatio();
|
||||
|
||||
/**
|
||||
* Get the preview (and output movie) duration.
|
||||
*
|
||||
* @return The duration of the preview (and output movie)
|
||||
*/
|
||||
public long getDuration();
|
||||
|
||||
/**
|
||||
* Render a frame according to the preview aspect ratio and activating all
|
||||
* storyboard items relative to the specified time.
|
||||
*
|
||||
* @param surfaceHolder SurfaceHolder used by the application
|
||||
* @param timeMs time corresponding to the frame to display
|
||||
*
|
||||
* @return The accurate time stamp of the frame that is rendered
|
||||
* .
|
||||
* @throws IllegalStateException if a preview or an export is already
|
||||
* in progress
|
||||
* @throws IllegalArgumentException if time is negative or beyond the
|
||||
* preview duration
|
||||
*/
|
||||
public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs);
|
||||
|
||||
/**
|
||||
* This method must be called after the aspect ratio of the project changes
|
||||
* and before startPreview is called. Note that this method may block for
|
||||
* an extensive period of time.
|
||||
*/
|
||||
public void generatePreview();
|
||||
|
||||
/**
|
||||
* Start the preview of all the storyboard items applied on all MediaItems
|
||||
* This method does not block (does not wait for the preview to complete).
|
||||
* The PreviewProgressListener allows to track the progress at the time
|
||||
* interval determined by the callbackAfterFrameCount parameter. The
|
||||
* SurfaceHolder has to be created and ready for use before calling this
|
||||
* method. The method is a no-op if there are no MediaItems in the
|
||||
* storyboard.
|
||||
*
|
||||
* @param surfaceHolder SurfaceHolder where the preview is rendered.
|
||||
* @param fromMs The time (relative to the timeline) at which the preview
|
||||
* will start
|
||||
* @param toMs The time (relative to the timeline) at which the preview will
|
||||
* stop. Use -1 to play to the end of the timeline
|
||||
* @param loop true if the preview should be looped once it reaches the end
|
||||
* @param callbackAfterFrameCount The listener interface should be invoked
|
||||
* after the number of frames specified by this parameter.
|
||||
* @param listener The listener which will be notified of the preview
|
||||
* progress
|
||||
* @throws IllegalArgumentException if fromMs is beyond the preview duration
|
||||
* @throws IllegalStateException if a preview or an export is already in
|
||||
* progress
|
||||
*/
|
||||
public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop,
|
||||
int callbackAfterFrameCount, PreviewProgressListener listener);
|
||||
|
||||
/**
|
||||
* Stop the current preview. This method blocks until ongoing preview is
|
||||
* stopped. Ignored if there is no preview running.
|
||||
*
|
||||
* @return The accurate current time when stop is effective expressed in
|
||||
* milliseconds
|
||||
*/
|
||||
public long stopPreview();
|
||||
}
|
||||
83
media/java/android/media/videoeditor/VideoEditorFactory.java
Executable file
83
media/java/android/media/videoeditor/VideoEditorFactory.java
Executable file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* The VideoEditorFactory class must be used to instantiate VideoEditor objects
|
||||
* by creating a new project {@link #create(String)} or by loading an
|
||||
* existing project {@link #load(String)}.
|
||||
* {@hide}
|
||||
*/
|
||||
public class VideoEditorFactory {
|
||||
/**
|
||||
* This is the factory method for creating a new VideoEditor instance.
|
||||
*
|
||||
* @param projectPath The path where all VideoEditor internal
|
||||
* files are stored. When a project is deleted the application is
|
||||
* responsible for deleting the path and its contents.
|
||||
*
|
||||
* @return The VideoEditor instance
|
||||
*
|
||||
* @throws IOException if path does not exist or if the path can
|
||||
* not be accessed in read/write mode
|
||||
* @throws IllegalStateException if a previous VideoEditor instance has not
|
||||
* been released
|
||||
*/
|
||||
public static VideoEditor create(String projectPath) throws IOException {
|
||||
// If the project path does not exist create it
|
||||
final File dir = new File(projectPath);
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdirs()) {
|
||||
throw new FileNotFoundException("Cannot create project path: " + projectPath);
|
||||
}
|
||||
}
|
||||
return new VideoEditorTestImpl(projectPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the factory method for instantiating a VideoEditor from the
|
||||
* internal state previously saved with the
|
||||
* {@link VideoEditor#save(String)} method.
|
||||
*
|
||||
* @param projectPath The path where all VideoEditor internal files
|
||||
* are stored. When a project is deleted the application is
|
||||
* responsible for deleting the path and its contents.
|
||||
* @param generatePreview if set to true the
|
||||
* {@link MediaEditor#generatePreview()} will be called internally to
|
||||
* generate any needed transitions.
|
||||
*
|
||||
* @return The VideoEditor instance
|
||||
*
|
||||
* @throws IOException if path does not exist or if the path can
|
||||
* not be accessed in read/write mode or if one of the resource
|
||||
* media files cannot be retrieved
|
||||
* @throws IllegalStateException if a previous VideoEditor instance has not
|
||||
* been released
|
||||
*/
|
||||
public static VideoEditor load(String projectPath, boolean generatePreview) throws IOException {
|
||||
final VideoEditorTestImpl videoEditor = new VideoEditorTestImpl(projectPath);
|
||||
if (generatePreview) {
|
||||
videoEditor.generatePreview();
|
||||
}
|
||||
return videoEditor;
|
||||
}
|
||||
}
|
||||
777
media/java/android/media/videoeditor/VideoEditorTestImpl.java
Normal file
777
media/java/android/media/videoeditor/VideoEditorTestImpl.java
Normal file
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* 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.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
/**
|
||||
* The VideoEditor implementation
|
||||
* {@hide}
|
||||
*/
|
||||
public class VideoEditorTestImpl implements VideoEditor {
|
||||
// Logging
|
||||
private static final String TAG = "VideoEditorImpl";
|
||||
|
||||
// The project filename
|
||||
private static final String PROJECT_FILENAME = "videoeditor.xml";
|
||||
|
||||
// XML tags
|
||||
private static final String TAG_PROJECT = "project";
|
||||
private static final String TAG_MEDIA_ITEMS = "media_items";
|
||||
private static final String TAG_MEDIA_ITEM = "media_item";
|
||||
private static final String ATTR_ID = "id";
|
||||
private static final String ATTR_FILENAME = "filename";
|
||||
private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "wavefoem";
|
||||
private static final String ATTR_RENDERING_MODE = "rendering_mode";
|
||||
private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
|
||||
private static final String ATTR_TYPE = "type";
|
||||
private static final String ATTR_DURATION = "duration";
|
||||
private static final String ATTR_BEGIN_TIME = "start_time";
|
||||
private static final String ATTR_END_TIME = "end_time";
|
||||
private static final String ATTR_VOLUME = "volume";
|
||||
|
||||
private static long mDurationMs;
|
||||
private final String mProjectPath;
|
||||
private final List<MediaItem> mMediaItems = new ArrayList<MediaItem>();
|
||||
private final List<AudioTrack> mAudioTracks = new ArrayList<AudioTrack>();
|
||||
private final List<Transition> mTransitions = new ArrayList<Transition>();
|
||||
private PreviewThread mPreviewThread;
|
||||
private int mAspectRatio;
|
||||
|
||||
/**
|
||||
* The preview thread
|
||||
*/
|
||||
private class PreviewThread extends Thread {
|
||||
// Instance variables
|
||||
private final static long FRAME_DURATION = 33;
|
||||
private final PreviewProgressListener mListener;
|
||||
private final int mCallbackAfterFrameCount;
|
||||
private final long mFromMs, mToMs;
|
||||
private boolean mRun, mLoop;
|
||||
private long mPositionMs;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param fromMs Start preview at this position
|
||||
* @param toMs The time (relative to the timeline) at which the preview
|
||||
* will stop. Use -1 to play to the end of the timeline
|
||||
* @param callbackAfterFrameCount The listener interface should be invoked
|
||||
* after the number of frames specified by this parameter.
|
||||
* @param loop true if the preview should be looped once it reaches the end
|
||||
* @param listener The listener
|
||||
*/
|
||||
public PreviewThread(long fromMs, long toMs, boolean loop, int callbackAfterFrameCount,
|
||||
PreviewProgressListener listener) {
|
||||
mPositionMs = mFromMs = fromMs;
|
||||
if (toMs < 0) {
|
||||
mToMs = mDurationMs;
|
||||
} else {
|
||||
mToMs = toMs;
|
||||
}
|
||||
mLoop = loop;
|
||||
mCallbackAfterFrameCount = callbackAfterFrameCount;
|
||||
mListener = listener;
|
||||
mRun = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "===> PreviewThread.run enter");
|
||||
}
|
||||
int frameCount = 0;
|
||||
while (mRun) {
|
||||
try {
|
||||
sleep(FRAME_DURATION);
|
||||
} catch (InterruptedException ex) {
|
||||
break;
|
||||
}
|
||||
frameCount++;
|
||||
mPositionMs += FRAME_DURATION;
|
||||
|
||||
if (mPositionMs >= mToMs) {
|
||||
if (!mLoop) {
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(VideoEditorTestImpl.this, mPositionMs, true);
|
||||
}
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "PreviewThread.run playback complete");
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// Fire a notification for the end of the clip
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(VideoEditorTestImpl.this, mToMs, false);
|
||||
}
|
||||
|
||||
// Rewind
|
||||
mPositionMs = mFromMs;
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(VideoEditorTestImpl.this, mPositionMs, false);
|
||||
}
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "PreviewThread.run playback complete");
|
||||
}
|
||||
frameCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (frameCount == mCallbackAfterFrameCount) {
|
||||
if (mListener != null) {
|
||||
mListener.onProgress(VideoEditorTestImpl.this, mPositionMs, false);
|
||||
}
|
||||
frameCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "===> PreviewThread.run exit");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the preview
|
||||
*
|
||||
* @return The stop position
|
||||
*/
|
||||
public long stopPreview() {
|
||||
mRun = false;
|
||||
try {
|
||||
join();
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
return mPositionMs;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param projectPath
|
||||
*/
|
||||
public VideoEditorTestImpl(String projectPath) throws IOException {
|
||||
mProjectPath = projectPath;
|
||||
final File projectXml = new File(projectPath, PROJECT_FILENAME);
|
||||
if (projectXml.exists()) {
|
||||
try {
|
||||
load();
|
||||
} catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
} else {
|
||||
mAspectRatio = MediaProperties.ASPECT_RATIO_16_9;
|
||||
mDurationMs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String getPath() {
|
||||
return mProjectPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void addMediaItem(MediaItem mediaItem) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
if (mMediaItems.contains(mediaItem)) {
|
||||
throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
|
||||
}
|
||||
|
||||
mMediaItems.add(mediaItem);
|
||||
computeTimelineDuration();
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
if (mMediaItems.contains(mediaItem)) {
|
||||
throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
|
||||
}
|
||||
|
||||
if (afterMediaItemId == null) {
|
||||
if (mMediaItems.size() > 0) {
|
||||
final MediaItem mi = mMediaItems.get(0);
|
||||
// Invalidate the transition at the beginning of the timeline
|
||||
removeTransitionBefore(mi);
|
||||
}
|
||||
mMediaItems.add(0, mediaItem);
|
||||
computeTimelineDuration();
|
||||
} else {
|
||||
final int mediaItemCount = mMediaItems.size();
|
||||
for (int i = 0; i < mediaItemCount; i++) {
|
||||
final MediaItem mi = mMediaItems.get(i);
|
||||
if (mi.getId().equals(afterMediaItemId)) {
|
||||
// Invalidate the transition at this position
|
||||
removeTransitionAfter(mi);
|
||||
// Insert the new media item
|
||||
mMediaItems.add(i+1, mediaItem);
|
||||
computeTimelineDuration();
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
final MediaItem moveMediaItem = removeMediaItem(mediaItemId);
|
||||
if (moveMediaItem == null) {
|
||||
throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId);
|
||||
}
|
||||
|
||||
if (afterMediaItemId == null) {
|
||||
if (mMediaItems.size() > 0) {
|
||||
final MediaItem mi = mMediaItems.get(0);
|
||||
// Invalidate adjacent transitions at the insertion point
|
||||
removeTransitionBefore(mi);
|
||||
// Insert the media item at the new position
|
||||
mMediaItems.add(0, moveMediaItem);
|
||||
computeTimelineDuration();
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot move media item (it is the only item)");
|
||||
}
|
||||
} else {
|
||||
final int mediaItemCount = mMediaItems.size();
|
||||
for (int i = 0; i < mediaItemCount; i++) {
|
||||
final MediaItem mi = mMediaItems.get(i);
|
||||
if (mi.getId().equals(afterMediaItemId)) {
|
||||
// Invalidate adjacent transitions at the insertion point
|
||||
removeTransitionAfter(mi);
|
||||
// Insert the media item at the new position
|
||||
mMediaItems.add(i+1, moveMediaItem);
|
||||
computeTimelineDuration();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized MediaItem removeMediaItem(String mediaItemId) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
final MediaItem mediaItem = getMediaItem(mediaItemId);
|
||||
if (mediaItem != null) {
|
||||
// Remove the media item
|
||||
mMediaItems.remove(mediaItem);
|
||||
// Remove the adjacent transitions
|
||||
removeAdjacentTransitions(mediaItem);
|
||||
computeTimelineDuration();
|
||||
}
|
||||
|
||||
return mediaItem;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized MediaItem getMediaItem(String mediaItemId) {
|
||||
for (MediaItem mediaItem : mMediaItems) {
|
||||
if (mediaItem.getId().equals(mediaItemId)) {
|
||||
return mediaItem;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized List<MediaItem> getAllMediaItems() {
|
||||
return mMediaItems;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void removeAllMediaItems() {
|
||||
mMediaItems.clear();
|
||||
|
||||
// Invalidate all transitions
|
||||
for (Transition transition : mTransitions) {
|
||||
transition.invalidate();
|
||||
}
|
||||
mTransitions.clear();
|
||||
|
||||
mDurationMs = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void addTransition(Transition transition) {
|
||||
// If a transition already exists at the specified position then
|
||||
// invalidate it.
|
||||
final Iterator<Transition> it = mTransitions.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Transition t = it.next();
|
||||
if (t.getAfterMediaItem() == transition.getAfterMediaItem()
|
||||
|| t.getBeforeMediaItem() == transition.getBeforeMediaItem()) {
|
||||
it.remove();
|
||||
t.invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mTransitions.add(transition);
|
||||
|
||||
// Cross reference the transitions
|
||||
final MediaItem afterMediaItem = transition.getAfterMediaItem();
|
||||
if (afterMediaItem != null) {
|
||||
afterMediaItem.setEndTransition(transition);
|
||||
}
|
||||
final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
|
||||
if (beforeMediaItem != null) {
|
||||
beforeMediaItem.setBeginTransition(transition);
|
||||
}
|
||||
computeTimelineDuration();
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized Transition removeTransition(String transitionId) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
final Transition transition = getTransition(transitionId);
|
||||
if (transition != null) {
|
||||
mTransitions.remove(transition);
|
||||
transition.invalidate();
|
||||
computeTimelineDuration();
|
||||
}
|
||||
|
||||
// Cross reference the transitions
|
||||
final MediaItem afterMediaItem = transition.getAfterMediaItem();
|
||||
if (afterMediaItem != null) {
|
||||
afterMediaItem.setEndTransition(null);
|
||||
}
|
||||
final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
|
||||
if (beforeMediaItem != null) {
|
||||
beforeMediaItem.setBeginTransition(null);
|
||||
}
|
||||
|
||||
return transition;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public List<Transition> getAllTransitions() {
|
||||
return mTransitions;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Transition getTransition(String transitionId) {
|
||||
for (Transition transition : mTransitions) {
|
||||
if (transition.getId().equals(transitionId)) {
|
||||
return transition;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void addAudioTrack(AudioTrack audioTrack) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
mAudioTracks.add(audioTrack);
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void insertAudioTrack(AudioTrack audioTrack, String afterAudioTrackId) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
if (afterAudioTrackId == null) {
|
||||
mAudioTracks.add(0, audioTrack);
|
||||
} else {
|
||||
final int audioTrackCount = mAudioTracks.size();
|
||||
for (int i = 0; i < audioTrackCount; i++) {
|
||||
AudioTrack at = mAudioTracks.get(i);
|
||||
if (at.getId().equals(afterAudioTrackId)) {
|
||||
mAudioTracks.add(i+1, audioTrack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) {
|
||||
throw new IllegalStateException("Not supported");
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized AudioTrack removeAudioTrack(String audioTrackId) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
final AudioTrack audioTrack = getAudioTrack(audioTrackId);
|
||||
if (audioTrack != null) {
|
||||
mAudioTracks.remove(audioTrack);
|
||||
}
|
||||
|
||||
return audioTrack;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public AudioTrack getAudioTrack(String audioTrackId) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
|
||||
final AudioTrack audioTrack = getAudioTrack(audioTrackId);
|
||||
if (audioTrack != null) {
|
||||
mAudioTracks.remove(audioTrack);
|
||||
}
|
||||
|
||||
return audioTrack;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public List<AudioTrack> getAllAudioTracks() {
|
||||
return mAudioTracks;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void save() throws IOException {
|
||||
final XmlSerializer serializer = Xml.newSerializer();
|
||||
final StringWriter writer = new StringWriter();
|
||||
serializer.setOutput(writer);
|
||||
serializer.startDocument("UTF-8", true);
|
||||
serializer.startTag("", TAG_PROJECT);
|
||||
serializer.attribute("", ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
|
||||
|
||||
serializer.startTag("", TAG_MEDIA_ITEMS);
|
||||
for (MediaItem mediaItem : mMediaItems) {
|
||||
serializer.startTag("", TAG_MEDIA_ITEM);
|
||||
serializer.attribute("", ATTR_ID, mediaItem.getId());
|
||||
serializer.attribute("", ATTR_TYPE, mediaItem.getClass().getSimpleName());
|
||||
serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
|
||||
serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(mediaItem.getRenderingMode()));
|
||||
if (mediaItem instanceof MediaVideoItem) {
|
||||
final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
|
||||
serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(mvi.getBoundaryBeginTime()));
|
||||
serializer.attribute("", ATTR_END_TIME, Long.toString(mvi.getBoundaryEndTime()));
|
||||
serializer.attribute("", ATTR_VOLUME, Integer.toString(mvi.getVolume()));
|
||||
if (mvi.getAudioWaveformFilename() != null) {
|
||||
serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME, mvi.getAudioWaveformFilename());
|
||||
}
|
||||
} else if (mediaItem instanceof MediaImageItem) {
|
||||
serializer.attribute("", ATTR_DURATION, Long.toString(mediaItem.getDuration()));
|
||||
}
|
||||
serializer.endTag("", TAG_MEDIA_ITEM);
|
||||
}
|
||||
serializer.endTag("", TAG_MEDIA_ITEMS);
|
||||
|
||||
serializer.endTag("", TAG_PROJECT);
|
||||
serializer.endDocument();
|
||||
|
||||
// Save the metadata XML file
|
||||
final FileOutputStream out = new FileOutputStream(new File(getPath(), PROJECT_FILENAME));
|
||||
out.write(writer.toString().getBytes());
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the project form XML
|
||||
*/
|
||||
private void load() throws FileNotFoundException, XmlPullParserException, IOException {
|
||||
final File file = new File(mProjectPath, PROJECT_FILENAME);
|
||||
// Load the metadata
|
||||
final XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(new FileInputStream(file), "UTF-8");
|
||||
int eventType = parser.getEventType();
|
||||
String name;
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
switch (eventType) {
|
||||
case XmlPullParser.START_TAG: {
|
||||
name = parser.getName();
|
||||
if (name.equals(TAG_PROJECT)) {
|
||||
mAspectRatio = Integer.parseInt(parser.getAttributeValue("",
|
||||
ATTR_ASPECT_RATIO));
|
||||
} else if (name.equals(TAG_MEDIA_ITEM)) {
|
||||
final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
|
||||
final String type = parser.getAttributeValue("", ATTR_TYPE);
|
||||
final String filename = parser.getAttributeValue("", ATTR_FILENAME);
|
||||
final int renderingMode = Integer.parseInt(parser.getAttributeValue("", ATTR_RENDERING_MODE));
|
||||
final MediaItem mediaItem;
|
||||
if (MediaImageItem.class.getSimpleName().equals(type)) {
|
||||
final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
|
||||
mediaItem = new MediaImageItem(mediaItemId, filename, durationMs,
|
||||
renderingMode);
|
||||
} else if (MediaVideoItem.class.getSimpleName().equals(type)) {
|
||||
final String audioWaveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
|
||||
mediaItem = new MediaVideoItem(mediaItemId, filename, renderingMode, audioWaveformFilename);
|
||||
|
||||
final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
|
||||
final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
|
||||
((MediaVideoItem)mediaItem).setExtractBoundaries(beginTimeMs, endTimeMs);
|
||||
|
||||
final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
|
||||
((MediaVideoItem)mediaItem).setVolume(volumePercent);
|
||||
} else {
|
||||
Log.e(TAG, "Unknown media item type: " + type);
|
||||
mediaItem = null;
|
||||
}
|
||||
mMediaItems.add(mediaItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
|
||||
computeTimelineDuration();
|
||||
}
|
||||
|
||||
public void cancelExport(String filename) {
|
||||
}
|
||||
|
||||
public void export(String filename, int height, int bitrate, ExportProgressListener listener)
|
||||
throws IOException {
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void generatePreview() {
|
||||
// Generate all the needed transitions
|
||||
for (Transition transition : mTransitions) {
|
||||
if (!transition.isGenerated()) {
|
||||
transition.generate();
|
||||
}
|
||||
}
|
||||
|
||||
// This is necessary because the user may had called setDuration on MediaImageItems
|
||||
computeTimelineDuration();
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void release() {
|
||||
stopPreview();
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public long getDuration() {
|
||||
// Since MediaImageItem can change duration we need to compute the duration here
|
||||
computeTimelineDuration();
|
||||
return mDurationMs;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int getAspectRatio() {
|
||||
return mAspectRatio;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void setAspectRatio(int aspectRatio) {
|
||||
mAspectRatio = aspectRatio;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs) {
|
||||
if (mPreviewThread != null) {
|
||||
throw new IllegalStateException("Previewing is in progress");
|
||||
}
|
||||
return timeMs;
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized void startPreview(SurfaceHolder surfaceHolder, long fromMs,
|
||||
long toMs, boolean loop, int callbackAfterFrameCount,
|
||||
PreviewProgressListener listener) {
|
||||
if (fromMs >= mDurationMs) {
|
||||
return;
|
||||
}
|
||||
mPreviewThread = new PreviewThread(fromMs, toMs, loop, callbackAfterFrameCount, listener);
|
||||
mPreviewThread.start();
|
||||
}
|
||||
|
||||
/*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized long stopPreview() {
|
||||
final long stopTimeMs;
|
||||
if (mPreviewThread != null) {
|
||||
stopTimeMs = mPreviewThread.stopPreview();
|
||||
mPreviewThread = null;
|
||||
} else {
|
||||
stopTimeMs = 0;
|
||||
}
|
||||
return stopTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the duration
|
||||
*/
|
||||
private void computeTimelineDuration() {
|
||||
mDurationMs = 0;
|
||||
for (MediaItem mediaItem : mMediaItems) {
|
||||
mDurationMs += mediaItem.getTimelineDuration();
|
||||
}
|
||||
|
||||
// Subtract the transition times
|
||||
for (Transition transition : mTransitions) {
|
||||
if (!(transition instanceof TransitionAtStart) && !(transition instanceof TransitionAtEnd)) {
|
||||
mDurationMs -= transition.getDuration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove transitions associated with the specified media item
|
||||
*
|
||||
* @param mediaItem The media item
|
||||
*/
|
||||
private void removeAdjacentTransitions(MediaItem mediaItem) {
|
||||
final Iterator<Transition> it = mTransitions.iterator();
|
||||
while (it.hasNext()) {
|
||||
Transition t = it.next();
|
||||
if (t.getAfterMediaItem() == mediaItem || t.getBeforeMediaItem() == mediaItem) {
|
||||
it.remove();
|
||||
t.invalidate();
|
||||
mediaItem.setBeginTransition(null);
|
||||
mediaItem.setEndTransition(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the transition before this media item
|
||||
*
|
||||
* @param mediaItem The media item
|
||||
*/
|
||||
private void removeTransitionBefore(MediaItem mediaItem) {
|
||||
final Iterator<Transition> it = mTransitions.iterator();
|
||||
while (it.hasNext()) {
|
||||
Transition t = it.next();
|
||||
if (t.getBeforeMediaItem() == mediaItem) {
|
||||
it.remove();
|
||||
t.invalidate();
|
||||
mediaItem.setBeginTransition(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the transition after this media item
|
||||
*
|
||||
* @param mediaItem The media item
|
||||
*/
|
||||
private void removeTransitionAfter(MediaItem mediaItem) {
|
||||
final Iterator<Transition> it = mTransitions.iterator();
|
||||
while (it.hasNext()) {
|
||||
Transition t = it.next();
|
||||
if (t.getAfterMediaItem() == mediaItem) {
|
||||
it.remove();
|
||||
t.invalidate();
|
||||
mediaItem.setEndTransition(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user