blob: 08d27d461f10c4a1637b03af0238eef49b7e1dac [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.videoeditor;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CancellationException;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
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;
/**
* Maximum supported file size
*/
public static final long MAX_SUPPORTED_FILE_SIZE = 2147483648L;
/**
* 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 overlayData The overlay data (null if the overlay data
* is unchanged)
*/
public void onProgress(VideoEditor videoEditor, long timeMs,
OverlayData overlayData);
/**
* This method notifies the listener when the preview is started
* previewing a project.
*
* @param videoEditor The VideoEditor instance
*/
public void onStart(VideoEditor videoEditor);
/**
* This method notifies the listener when the preview is stopped
* previewing a project.
*
* @param videoEditor The VideoEditor instance
*/
public void onStop(VideoEditor videoEditor);
/**
* This method notifies the listener when error has occurred during
* previewing a project.
*
* @param videoEditor The VideoEditor instance
* @param error The error that has occurred
* FIXME: We should pass well-defined error code to the application;
* but for now, we just pass whatever error code reported by the native layer.
*/
public void onError(VideoEditor videoEditor, int error);
}
/**
* 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);
}
public interface MediaProcessingProgressListener {
/**
* Values used for the action parameter
*/
public static final int ACTION_ENCODE = 1;
public static final int ACTION_DECODE = 2;
/**
* This method notifies the listener of the progress status of
* processing a media object such as a Transition, AudioTrack & Kenburns
* This method may be called maximum 100 times for one operation.
*
* @param object The object that is being processed such as a Transition
* or AudioTrack
* @param action The type of processing being performed
* @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(Object item, int action, int progress);
}
/**
* The overlay data
*/
public static final class OverlayData {
// Instance variables
private Bitmap mOverlayBitmap;
private int mRenderingMode;
private boolean mClear;
private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
/**
* Default constructor
*/
public OverlayData() {
mOverlayBitmap = null;
mRenderingMode = MediaArtistNativeHelper.MediaRendering.BLACK_BORDERS;
mClear = false;
}
/**
* Releases the bitmap
*/
public void release() {
if (mOverlayBitmap != null) {
mOverlayBitmap.recycle();
mOverlayBitmap = null;
}
}
/**
* Check if the overlay needs to be rendered
*
* @return true if rendering is needed
*/
public boolean needsRendering() {
return (mClear || mOverlayBitmap != null);
}
/**
* Store the overlay data
*
* @param overlayBitmap The overlay bitmap
* @param renderingMode The rendering mode
*/
void set(Bitmap overlayBitmap, int renderingMode) {
mOverlayBitmap = overlayBitmap;
mRenderingMode = renderingMode;
mClear = false;
}
/**
* Clear the overlay
*/
void setClear() {
mClear = true;
}
/**
* Render the overlay by either clearing it or by
* rendering the overlay bitmap with the specified
* rendering mode
*
* @param destBitmap The destination bitmap
*/
public void renderOverlay(Bitmap destBitmap) {
if (mClear) {
destBitmap.eraseColor(Color.TRANSPARENT);
} else if (mOverlayBitmap != null) {
final Canvas overlayCanvas = new Canvas(destBitmap);
final Rect destRect;
final Rect srcRect;
switch (mRenderingMode) {
case MediaArtistNativeHelper.MediaRendering.RESIZING: {
destRect = new Rect(0, 0, overlayCanvas.getWidth(),
overlayCanvas.getHeight());
srcRect = new Rect(0, 0, mOverlayBitmap.getWidth(),
mOverlayBitmap.getHeight());
break;
}
case MediaArtistNativeHelper.MediaRendering.BLACK_BORDERS: {
int left, right, top, bottom;
float aROverlayImage, aRCanvas;
aROverlayImage = (float)(mOverlayBitmap.getWidth()) /
(float)(mOverlayBitmap.getHeight());
aRCanvas = (float)(overlayCanvas.getWidth()) /
(float)(overlayCanvas.getHeight());
if (aROverlayImage > aRCanvas) {
int newHeight = ((overlayCanvas.getWidth() * mOverlayBitmap.getHeight())
/ mOverlayBitmap.getWidth());
left = 0;
top = (overlayCanvas.getHeight() - newHeight) / 2;
right = overlayCanvas.getWidth();
bottom = top + newHeight;
} else {
int newWidth = ((overlayCanvas.getHeight() * mOverlayBitmap.getWidth())
/ mOverlayBitmap.getHeight());
left = (overlayCanvas.getWidth() - newWidth) / 2;
top = 0;
right = left + newWidth;
bottom = overlayCanvas.getHeight();
}
destRect = new Rect(left, top, right, bottom);
srcRect = new Rect(0, 0, mOverlayBitmap.getWidth(), mOverlayBitmap.getHeight());
break;
}
case MediaArtistNativeHelper.MediaRendering.CROPPING: {
// Calculate the source rect
int left, right, top, bottom;
float aROverlayImage, aRCanvas;
aROverlayImage = (float)(mOverlayBitmap.getWidth()) /
(float)(mOverlayBitmap.getHeight());
aRCanvas = (float)(overlayCanvas.getWidth()) /
(float)(overlayCanvas.getHeight());
if (aROverlayImage < aRCanvas) {
int newHeight = ((mOverlayBitmap.getWidth() * overlayCanvas.getHeight())
/ overlayCanvas.getWidth());
left = 0;
top = (mOverlayBitmap.getHeight() - newHeight) / 2;
right = mOverlayBitmap.getWidth();
bottom = top + newHeight;
} else {
int newWidth = ((mOverlayBitmap.getHeight() * overlayCanvas.getWidth())
/ overlayCanvas.getHeight());
left = (mOverlayBitmap.getWidth() - newWidth) / 2;
top = 0;
right = left + newWidth;
bottom = mOverlayBitmap.getHeight();
}
srcRect = new Rect(left, top, right, bottom);
destRect = new Rect(0, 0, overlayCanvas.getWidth(), overlayCanvas.getHeight());
break;
}
default: {
throw new IllegalStateException("Rendering mode: " + mRenderingMode);
}
}
destBitmap.eraseColor(Color.TRANSPARENT);
overlayCanvas.drawBitmap(mOverlayBitmap, srcRect, destRect, sResizePaint);
mOverlayBitmap.recycle();
}
}
}
/**
* @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.
*
* @throws IOException if the internal state cannot be saved to project file
*/
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.
*
* The audio and video codecs are automatically selected by the underlying
* implementation.
*
* @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
* or if the audio or video codecs 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;
/**
* 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 audioCodec The audio codec to be used for the export. The audio
* codec values are defined in the MediaProperties class (e.g.
* ACODEC_AAC_LC). Note that not all audio codec types are
* supported for export purposes.
* @param videoCodec The video codec to be used for the export. The video
* codec values are defined in the MediaProperties class (e.g.
* VCODEC_H264). Note that not all video codec types are
* supported for export purposes.
* @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
* or if the audio or video codecs 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 cancelled by calling
* {@link #cancelExport()}
* @throws UnsupportOperationException if multiple simultaneous export() are
* not allowed
*/
public void export(String filename, int height, int bitrate, int audioCodec,
int videoCodec, ExportProgressListener listener)
throws IOException;
/**
* Cancel the running export operation. This method blocks until the export
* is cancelled 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
* @param overlayData The overlay data
*
* @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,
OverlayData overlayData);
/**
* This method must be called after any changes made to the storyboard
* and before startPreview is called. Note that this method may block for an
* extensive period of time.
*/
public void generatePreview(MediaProcessingProgressListener listener);
/**
* 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();
/**
* Clears the preview surface
*
* @param surfaceHolder SurfaceHolder where the preview is rendered
* and needs to be cleared.
*/
public void clearSurface(SurfaceHolder surfaceHolder);
}