diff options
| author | Gil Dobjanschi <virgild@google.com> | 2010-09-07 11:16:24 -0700 |
|---|---|---|
| committer | Gil Dobjanschi <virgild@google.com> | 2010-09-16 15:19:46 -0700 |
| commit | fdacc8be92cd36f712cfdb0fcf9b0e847f8eeb58 (patch) | |
| tree | e442535f0ed7f66dfeac057e36c3595629835865 | |
| parent | d0fa371f276fde32d81c037006941bc93da0bb03 (diff) | |
| download | frameworks_base-fdacc8be92cd36f712cfdb0fcf9b0e847f8eeb58.zip frameworks_base-fdacc8be92cd36f712cfdb0fcf9b0e847f8eeb58.tar.gz frameworks_base-fdacc8be92cd36f712cfdb0fcf9b0e847f8eeb58.tar.bz2 | |
Initial Video Editor API
Change-Id: Iaa91e78d0e50f45ceb943bab93c4f1ea1bdee003
21 files changed, 4328 insertions, 0 deletions
diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java new file mode 100755 index 0000000..9d40a78 --- /dev/null +++ b/media/java/android/media/videoeditor/AudioTrack.java @@ -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();
+ }
+}
diff --git a/media/java/android/media/videoeditor/Effect.java b/media/java/android/media/videoeditor/Effect.java new file mode 100755 index 0000000..177b863 --- /dev/null +++ b/media/java/android/media/videoeditor/Effect.java @@ -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();
+ }
+}
diff --git a/media/java/android/media/videoeditor/EffectColor.java b/media/java/android/media/videoeditor/EffectColor.java new file mode 100755 index 0000000..7c61627 --- /dev/null +++ b/media/java/android/media/videoeditor/EffectColor.java @@ -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;
+ }
+}
diff --git a/media/java/android/media/videoeditor/EffectKenBurns.java b/media/java/android/media/videoeditor/EffectKenBurns.java new file mode 100755 index 0000000..c6d22f4 --- /dev/null +++ b/media/java/android/media/videoeditor/EffectKenBurns.java @@ -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;
+ }
+}
diff --git a/media/java/android/media/videoeditor/ExtractAudioWaveformProgressListener.java b/media/java/android/media/videoeditor/ExtractAudioWaveformProgressListener.java new file mode 100644 index 0000000..1cce148 --- /dev/null +++ b/media/java/android/media/videoeditor/ExtractAudioWaveformProgressListener.java @@ -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); +} + diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java new file mode 100755 index 0000000..db7585a --- /dev/null +++ b/media/java/android/media/videoeditor/MediaImageItem.java @@ -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;
+ }
+}
diff --git a/media/java/android/media/videoeditor/MediaItem.java b/media/java/android/media/videoeditor/MediaItem.java new file mode 100755 index 0000000..9e32744 --- /dev/null +++ b/media/java/android/media/videoeditor/MediaItem.java @@ -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();
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/videoeditor/MediaProperties.java b/media/java/android/media/videoeditor/MediaProperties.java new file mode 100755 index 0000000..c3f5ef7 --- /dev/null +++ b/media/java/android/media/videoeditor/MediaProperties.java @@ -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;
+ }
+}
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java new file mode 100755 index 0000000..87e9a22 --- /dev/null +++ b/media/java/android/media/videoeditor/MediaVideoItem.java @@ -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;
+ }
+}
diff --git a/media/java/android/media/videoeditor/Overlay.java b/media/java/android/media/videoeditor/Overlay.java new file mode 100755 index 0000000..5065636 --- /dev/null +++ b/media/java/android/media/videoeditor/Overlay.java @@ -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();
+ }
+}
diff --git a/media/java/android/media/videoeditor/OverlayFrame.java b/media/java/android/media/videoeditor/OverlayFrame.java new file mode 100755 index 0000000..e5d9b81 --- /dev/null +++ b/media/java/android/media/videoeditor/OverlayFrame.java @@ -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;
+ }
+}
diff --git a/media/java/android/media/videoeditor/Transition.java b/media/java/android/media/videoeditor/Transition.java new file mode 100755 index 0000000..e4bc9a4 --- /dev/null +++ b/media/java/android/media/videoeditor/Transition.java @@ -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();
+ }
+}
diff --git a/media/java/android/media/videoeditor/TransitionAlpha.java b/media/java/android/media/videoeditor/TransitionAlpha.java new file mode 100755 index 0000000..0a4a12f --- /dev/null +++ b/media/java/android/media/videoeditor/TransitionAlpha.java @@ -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() {
+ }
+}
diff --git a/media/java/android/media/videoeditor/TransitionAtEnd.java b/media/java/android/media/videoeditor/TransitionAtEnd.java new file mode 100755 index 0000000..7765bd4 --- /dev/null +++ b/media/java/android/media/videoeditor/TransitionAtEnd.java @@ -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() {
+ }
+}
diff --git a/media/java/android/media/videoeditor/TransitionAtStart.java b/media/java/android/media/videoeditor/TransitionAtStart.java new file mode 100755 index 0000000..65ebd01 --- /dev/null +++ b/media/java/android/media/videoeditor/TransitionAtStart.java @@ -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() {
+ }
+}
diff --git a/media/java/android/media/videoeditor/TransitionCrossfade.java b/media/java/android/media/videoeditor/TransitionCrossfade.java new file mode 100755 index 0000000..f8223e8 --- /dev/null +++ b/media/java/android/media/videoeditor/TransitionCrossfade.java @@ -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() {
+ }
+}
diff --git a/media/java/android/media/videoeditor/TransitionFadeToBlack.java b/media/java/android/media/videoeditor/TransitionFadeToBlack.java new file mode 100755 index 0000000..9569a65 --- /dev/null +++ b/media/java/android/media/videoeditor/TransitionFadeToBlack.java @@ -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() {
+ }
+}
diff --git a/media/java/android/media/videoeditor/TransitionSliding.java b/media/java/android/media/videoeditor/TransitionSliding.java new file mode 100755 index 0000000..cc9f4b2 --- /dev/null +++ b/media/java/android/media/videoeditor/TransitionSliding.java @@ -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() {
+ }
+}
diff --git a/media/java/android/media/videoeditor/VideoEditor.java b/media/java/android/media/videoeditor/VideoEditor.java new file mode 100755 index 0000000..aa8f2cb --- /dev/null +++ b/media/java/android/media/videoeditor/VideoEditor.java @@ -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();
+}
diff --git a/media/java/android/media/videoeditor/VideoEditorFactory.java b/media/java/android/media/videoeditor/VideoEditorFactory.java new file mode 100755 index 0000000..2c56fc2 --- /dev/null +++ b/media/java/android/media/videoeditor/VideoEditorFactory.java @@ -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; + } +} diff --git a/media/java/android/media/videoeditor/VideoEditorTestImpl.java b/media/java/android/media/videoeditor/VideoEditorTestImpl.java new file mode 100644 index 0000000..a23c5c6 --- /dev/null +++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java @@ -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; + } + } + } +} |
