Initial Video Editor API

Change-Id: Iaa91e78d0e50f45ceb943bab93c4f1ea1bdee003
This commit is contained in:
Gil Dobjanschi
2010-09-07 11:16:24 -07:00
parent d0fa371f27
commit fdacc8be92
21 changed files with 4328 additions and 0 deletions

View 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();
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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);
}

View 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;
}
}

View 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();
}
}
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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;
}
}

View 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();
}
}

View 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() {
}
}

View 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() {
}
}

View 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() {
}
}

View 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() {
}
}

View 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() {
}
}

View 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() {
}
}

View 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();
}

View 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;
}
}

View 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;
}
}
}
}