blob: fbf2eaba7da7db317ed256fecedd7374c483c91d [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.videoeditor;
import java.io.File;
import java.io.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 java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.media.videoeditor.MediaImageItem;
import android.media.videoeditor.MediaItem;
import android.media.MediaMetadataRetriever;
import android.util.Log;
import android.util.Xml;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.os.Debug;
import android.os.SystemProperties;
import android.os.Environment;
import libcore.io.IoUtils;
/**
* The VideoEditor implementation {@hide}
*/
public class VideoEditorImpl 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 TAG_TRANSITIONS = "transitions";
private static final String TAG_TRANSITION = "transition";
private static final String TAG_OVERLAYS = "overlays";
private static final String TAG_OVERLAY = "overlay";
private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes";
private static final String TAG_EFFECTS = "effects";
private static final String TAG_EFFECT = "effect";
private static final String TAG_AUDIO_TRACKS = "audio_tracks";
private static final String TAG_AUDIO_TRACK = "audio_track";
private static final String ATTR_ID = "id";
private static final String ATTR_FILENAME = "filename";
private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "waveform";
private static final String ATTR_RENDERING_MODE = "rendering_mode";
private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
private static final String ATTR_REGENERATE_PCM = "regeneratePCMFlag";
private static final String ATTR_TYPE = "type";
private static final String ATTR_DURATION = "duration";
private static final String ATTR_START_TIME = "start_time";
private static final String ATTR_BEGIN_TIME = "begin_time";
private static final String ATTR_END_TIME = "end_time";
private static final String ATTR_VOLUME = "volume";
private static final String ATTR_BEHAVIOR = "behavior";
private static final String ATTR_DIRECTION = "direction";
private static final String ATTR_BLENDING = "blending";
private static final String ATTR_INVERT = "invert";
private static final String ATTR_MASK = "mask";
private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
private static final String ATTR_COLOR_EFFECT_TYPE = "color_type";
private static final String ATTR_COLOR_EFFECT_VALUE = "color_value";
private static final String ATTR_START_RECT_LEFT = "start_l";
private static final String ATTR_START_RECT_TOP = "start_t";
private static final String ATTR_START_RECT_RIGHT = "start_r";
private static final String ATTR_START_RECT_BOTTOM = "start_b";
private static final String ATTR_END_RECT_LEFT = "end_l";
private static final String ATTR_END_RECT_TOP = "end_t";
private static final String ATTR_END_RECT_RIGHT = "end_r";
private static final String ATTR_END_RECT_BOTTOM = "end_b";
private static final String ATTR_LOOP = "loop";
private static final String ATTR_MUTED = "muted";
private static final String ATTR_DUCK_ENABLED = "ducking_enabled";
private static final String ATTR_DUCK_THRESHOLD = "ducking_threshold";
private static final String ATTR_DUCKED_TRACK_VOLUME = "ducking_volume";
private static final String ATTR_GENERATED_IMAGE_CLIP = "generated_image_clip";
private static final String ATTR_IS_IMAGE_CLIP_GENERATED = "is_image_clip_generated";
private static final String ATTR_GENERATED_TRANSITION_CLIP = "generated_transition_clip";
private static final String ATTR_IS_TRANSITION_GENERATED = "is_transition_generated";
private static final String ATTR_OVERLAY_RGB_FILENAME = "overlay_rgb_filename";
private static final String ATTR_OVERLAY_FRAME_WIDTH = "overlay_frame_width";
private static final String ATTR_OVERLAY_FRAME_HEIGHT = "overlay_frame_height";
private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH = "resized_RGBframe_width";
private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT = "resized_RGBframe_height";
private static final int ENGINE_ACCESS_MAX_TIMEOUT_MS = 500;
/*
* Instance variables
*/
private final Semaphore mLock;
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 long mDurationMs;
private int mAspectRatio;
/*
* Private Object for calling native Methods via MediaArtistNativeHelper
*/
private MediaArtistNativeHelper mMANativeHelper;
private boolean mPreviewInProgress = false;
private final boolean mMallocDebug;
/**
* Constructor
*
* @param projectPath - The path where the VideoEditor stores all files
* related to the project
*/
public VideoEditorImpl(String projectPath) throws IOException {
String s;
s = SystemProperties.get("libc.debug.malloc");
if (s.equals("1")) {
mMallocDebug = true;
try {
dumpHeap("HeapAtStart");
} catch (Exception ex) {
Log.e(TAG, "dumpHeap returned error in constructor");
}
} else {
mMallocDebug = false;
}
mLock = new Semaphore(1, true);
mMANativeHelper = new MediaArtistNativeHelper(projectPath, mLock, this);
mProjectPath = projectPath;
final File projectXml = new File(projectPath, PROJECT_FILENAME);
if (projectXml.exists()) {
try {
load();
} catch (Exception ex) {
ex.printStackTrace();
throw new IOException(ex.toString());
}
} else {
mAspectRatio = MediaProperties.ASPECT_RATIO_16_9;
mDurationMs = 0;
}
}
/*
* @return The MediaArtistNativeHelper object
*/
MediaArtistNativeHelper getNativeContext() {
return mMANativeHelper;
}
/*
* {@inheritDoc}
*/
public synchronized void addAudioTrack(AudioTrack audioTrack) {
if (audioTrack == null) {
throw new IllegalArgumentException("Audio Track is null");
}
if (mAudioTracks.size() == 1) {
throw new IllegalArgumentException("No more tracks can be added");
}
mMANativeHelper.setGeneratePreview(true);
/*
* Add the audio track to AudioTrack list
*/
mAudioTracks.add(audioTrack);
/*
* Form the audio PCM file path
*/
final String audioTrackPCMFilePath = String.format(mProjectPath + "/"
+ "AudioPcm" + audioTrack.getId() + ".pcm");
/*
* Create PCM only if not generated in previous session
*/
if (new File(audioTrackPCMFilePath).exists()) {
mMANativeHelper.setAudioflag(false);
}
}
/*
* {@inheritDoc}
*/
public synchronized void addMediaItem(MediaItem mediaItem) {
/*
* Validate Media Item
*/
if (mediaItem == null) {
throw new IllegalArgumentException("Media item is null");
}
/*
* Add the Media item to MediaItem list
*/
if (mMediaItems.contains(mediaItem)) {
throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
}
mMANativeHelper.setGeneratePreview(true);
/*
* Invalidate the end transition if necessary
*/
final int mediaItemsCount = mMediaItems.size();
if (mediaItemsCount > 0) {
removeTransitionAfter(mediaItemsCount - 1);
}
/*
* Add the new media item
*/
mMediaItems.add(mediaItem);
computeTimelineDuration();
/*
* Generate project thumbnail only from first media Item on storyboard
*/
if (mMediaItems.size() == 1) {
generateProjectThumbnail();
}
}
/*
* {@inheritDoc}
*/
public synchronized void addTransition(Transition transition) {
if (transition == null) {
throw new IllegalArgumentException("Null Transition");
}
final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
final MediaItem afterMediaItem = transition.getAfterMediaItem();
/*
* Check if the MediaItems are in sequence
*/
if (mMediaItems == null) {
throw new IllegalArgumentException("No media items are added");
}
if ((afterMediaItem != null) && (beforeMediaItem != null)) {
final int afterMediaItemIndex = mMediaItems.indexOf(afterMediaItem);
final int beforeMediaItemIndex = mMediaItems.indexOf(beforeMediaItem);
if ((afterMediaItemIndex == -1) || (beforeMediaItemIndex == -1)) {
throw new IllegalArgumentException
("Either of the mediaItem is not found in the list");
}
if (afterMediaItemIndex != (beforeMediaItemIndex - 1) ) {
throw new IllegalArgumentException("MediaItems are not in sequence");
}
}
mMANativeHelper.setGeneratePreview(true);
mTransitions.add(transition);
/*
* Cross reference the transitions
*/
if (afterMediaItem != null) {
/*
* If a transition already exists at the specified position then
* invalidate it.
*/
if (afterMediaItem.getEndTransition() != null) {
afterMediaItem.getEndTransition().invalidate();
mTransitions.remove(afterMediaItem.getEndTransition());
}
afterMediaItem.setEndTransition(transition);
}
if (beforeMediaItem != null) {
/*
* If a transition already exists at the specified position then
* invalidate it.
*/
if (beforeMediaItem.getBeginTransition() != null) {
beforeMediaItem.getBeginTransition().invalidate();
mTransitions.remove(beforeMediaItem.getBeginTransition());
}
beforeMediaItem.setBeginTransition(transition);
}
computeTimelineDuration();
}
/*
* {@inheritDoc}
*/
public void cancelExport(String filename) {
if (mMANativeHelper != null && filename != null) {
mMANativeHelper.stop(filename);
}
}
/*
* {@inheritDoc}
*/
public void export(String filename, int height, int bitrate,
int audioCodec, int videoCodec,
ExportProgressListener listener)
throws IOException {
int audcodec = 0;
int vidcodec = 0;
if (filename == null) {
throw new IllegalArgumentException("export: filename is null");
}
final File tempPathFile = new File(filename);
if (tempPathFile == null) {
throw new IOException(filename + "can not be created");
}
if (mMediaItems.size() == 0) {
throw new IllegalStateException("No MediaItems added");
}
switch (height) {
case MediaProperties.HEIGHT_144:
break;
case MediaProperties.HEIGHT_288:
break;
case MediaProperties.HEIGHT_360:
break;
case MediaProperties.HEIGHT_480:
break;
case MediaProperties.HEIGHT_720:
break;
case MediaProperties.HEIGHT_1080:
break;
default: {
String message = "Unsupported height value " + height;
throw new IllegalArgumentException(message);
}
}
switch (bitrate) {
case MediaProperties.BITRATE_28K:
break;
case MediaProperties.BITRATE_40K:
break;
case MediaProperties.BITRATE_64K:
break;
case MediaProperties.BITRATE_96K:
break;
case MediaProperties.BITRATE_128K:
break;
case MediaProperties.BITRATE_192K:
break;
case MediaProperties.BITRATE_256K:
break;
case MediaProperties.BITRATE_384K:
break;
case MediaProperties.BITRATE_512K:
break;
case MediaProperties.BITRATE_800K:
break;
case MediaProperties.BITRATE_2M:
break;
case MediaProperties.BITRATE_5M:
break;
case MediaProperties.BITRATE_8M:
break;
default: {
final String message = "Unsupported bitrate value " + bitrate;
throw new IllegalArgumentException(message);
}
}
computeTimelineDuration();
final long audioBitrate = MediaArtistNativeHelper.Bitrate.BR_96_KBPS;
final long fileSize = (mDurationMs * (bitrate + audioBitrate)) / 8000;
if (MAX_SUPPORTED_FILE_SIZE <= fileSize) {
throw new IllegalStateException("Export Size is more than 2GB");
}
switch (audioCodec) {
case MediaProperties.ACODEC_AAC_LC:
audcodec = MediaArtistNativeHelper.AudioFormat.AAC;
break;
case MediaProperties.ACODEC_AMRNB:
audcodec = MediaArtistNativeHelper.AudioFormat.AMR_NB;
break;
default: {
String message = "Unsupported audio codec type " + audioCodec;
throw new IllegalArgumentException(message);
}
}
switch (videoCodec) {
case MediaProperties.VCODEC_H263:
vidcodec = MediaArtistNativeHelper.VideoFormat.H263;
break;
case MediaProperties.VCODEC_H264:
vidcodec = MediaArtistNativeHelper.VideoFormat.H264;
break;
case MediaProperties.VCODEC_MPEG4:
vidcodec = MediaArtistNativeHelper.VideoFormat.MPEG4;
break;
default: {
String message = "Unsupported video codec type " + videoCodec;
throw new IllegalArgumentException(message);
}
}
boolean semAcquireDone = false;
try {
lock();
semAcquireDone = true;
if (mMANativeHelper == null) {
throw new IllegalStateException("The video editor is not initialized");
}
mMANativeHelper.setAudioCodec(audcodec);
mMANativeHelper.setVideoCodec(vidcodec);
mMANativeHelper.export(filename, mProjectPath, height,bitrate,
mMediaItems, mTransitions, mAudioTracks, listener);
} catch (InterruptedException ex) {
Log.e(TAG, "Sem acquire NOT successful in export");
} finally {
if (semAcquireDone) {
unlock();
}
}
}
/*
* {@inheritDoc}
*/
public void export(String filename, int height, int bitrate,
ExportProgressListener listener)
throws IOException {
int defaultAudiocodec = MediaArtistNativeHelper.AudioFormat.AAC;
int defaultVideocodec = MediaArtistNativeHelper.VideoFormat.H264;
export(filename, height, bitrate, defaultAudiocodec,
defaultVideocodec, listener);
}
/*
* {@inheritDoc}
*/
public void generatePreview(MediaProcessingProgressListener listener) {
boolean semAcquireDone = false;
try {
lock();
semAcquireDone = true;
if (mMANativeHelper == null) {
throw new IllegalStateException("The video editor is not initialized");
}
if ((mMediaItems.size() > 0) || (mAudioTracks.size() > 0)) {
mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, mAudioTracks,
listener);
}
} catch (InterruptedException ex) {
Log.e(TAG, "Sem acquire NOT successful in previewStoryBoard");
} finally {
if (semAcquireDone) {
unlock();
}
}
}
/*
* {@inheritDoc}
*/
public List<AudioTrack> getAllAudioTracks() {
return mAudioTracks;
}
/*
* {@inheritDoc}
*/
public List<MediaItem> getAllMediaItems() {
return mMediaItems;
}
/*
* {@inheritDoc}
*/
public List<Transition> getAllTransitions() {
return mTransitions;
}
/*
* {@inheritDoc}
*/
public int getAspectRatio() {
return mAspectRatio;
}
/*
* {@inheritDoc}
*/
public AudioTrack getAudioTrack(String audioTrackId) {
for (AudioTrack at : mAudioTracks) {
if (at.getId().equals(audioTrackId)) {
return at;
}
}
return null;
}
/*
* {@inheritDoc}
*/
public long getDuration() {
/**
* Since MediaImageItem can change duration we need to compute the
* duration here
*/
computeTimelineDuration();
return mDurationMs;
}
/*
* Force updates the timeline duration
*/
void updateTimelineDuration() {
computeTimelineDuration();
}
/*
* {@inheritDoc}
*/
public synchronized MediaItem getMediaItem(String mediaItemId) {
for (MediaItem mediaItem : mMediaItems) {
if (mediaItem.getId().equals(mediaItemId)) {
return mediaItem;
}
}
return null;
}
/*
* {@inheritDoc}
*/
public String getPath() {
return mProjectPath;
}
/*
* {@inheritDoc}
*/
public Transition getTransition(String transitionId) {
for (Transition transition : mTransitions) {
if (transition.getId().equals(transitionId)) {
return transition;
}
}
return null;
}
/*
* {@inheritDoc}
*/
public synchronized void insertAudioTrack(AudioTrack audioTrack,
String afterAudioTrackId) {
if (mAudioTracks.size() == 1) {
throw new IllegalArgumentException("No more tracks can be added");
}
if (afterAudioTrackId == null) {
mMANativeHelper.setGeneratePreview(true);
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)) {
mMANativeHelper.setGeneratePreview(true);
mAudioTracks.add(i + 1, audioTrack);
return;
}
}
throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId);
}
}
/*
* {@inheritDoc}
*/
public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) {
if (mMediaItems.contains(mediaItem)) {
throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
}
if (afterMediaItemId == null) {
mMANativeHelper.setGeneratePreview(true);
if (mMediaItems.size() > 0) {
/**
* Invalidate the transition at the beginning of the timeline
*/
removeTransitionBefore(0);
}
mMediaItems.add(0, mediaItem);
computeTimelineDuration();
generateProjectThumbnail();
} else {
final int mediaItemCount = mMediaItems.size();
for (int i = 0; i < mediaItemCount; i++) {
final MediaItem mi = mMediaItems.get(i);
if (mi.getId().equals(afterMediaItemId)) {
mMANativeHelper.setGeneratePreview(true);
/**
* Invalidate the transition at this position
*/
removeTransitionAfter(i);
/**
* Insert the new media item
*/
mMediaItems.add(i + 1, mediaItem);
computeTimelineDuration();
return;
}
}
throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
}
}
/*
* {@inheritDoc}
*/
public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) {
throw new IllegalStateException("Not supported");
}
/*
* {@inheritDoc}
*/
public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) {
final MediaItem moveMediaItem = removeMediaItem(mediaItemId,true);
if (moveMediaItem == null) {
throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId);
}
if (afterMediaItemId == null) {
if (mMediaItems.size() > 0) {
mMANativeHelper.setGeneratePreview(true);
/**
* Invalidate adjacent transitions at the insertion point
*/
removeTransitionBefore(0);
/**
* Insert the media item at the new position
*/
mMediaItems.add(0, moveMediaItem);
computeTimelineDuration();
generateProjectThumbnail();
} 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)) {
mMANativeHelper.setGeneratePreview(true);
/**
* Invalidate adjacent transitions at the insertion point
*/
removeTransitionAfter(i);
/**
* Insert the media item at the new position
*/
mMediaItems.add(i + 1, moveMediaItem);
computeTimelineDuration();
return;
}
}
throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
}
}
/*
* {@inheritDoc}
*/
public void release() {
stopPreview();
boolean semAcquireDone = false;
try {
lock();
semAcquireDone = true;
if (mMANativeHelper != null) {
mMediaItems.clear();
mAudioTracks.clear();
mTransitions.clear();
mMANativeHelper.releaseNativeHelper();
mMANativeHelper = null;
}
} catch (Exception ex) {
Log.e(TAG, "Sem acquire NOT successful in export", ex);
} finally {
if (semAcquireDone) {
unlock();
}
}
if (mMallocDebug) {
try {
dumpHeap("HeapAtEnd");
} catch (Exception ex) {
Log.e(TAG, "dumpHeap returned error in release");
}
}
}
/*
* {@inheritDoc}
*/
public synchronized void removeAllMediaItems() {
mMANativeHelper.setGeneratePreview(true);
mMediaItems.clear();
/**
* Invalidate all transitions
*/
for (Transition transition : mTransitions) {
transition.invalidate();
}
mTransitions.clear();
mDurationMs = 0;
/**
* If a thumbnail already exists, then delete it
*/
if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
(new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
}
}
/*
* {@inheritDoc}
*/
public synchronized AudioTrack removeAudioTrack(String audioTrackId) {
final AudioTrack audioTrack = getAudioTrack(audioTrackId);
if (audioTrack != null) {
mMANativeHelper.setGeneratePreview(true);
mAudioTracks.remove(audioTrack);
audioTrack.invalidate();
mMANativeHelper.invalidatePcmFile();
mMANativeHelper.setAudioflag(true);
} else {
throw new IllegalArgumentException(" No more audio tracks");
}
return audioTrack;
}
/*
* {@inheritDoc}
*/
public synchronized MediaItem removeMediaItem(String mediaItemId) {
final String firstItemString = mMediaItems.get(0).getId();
final MediaItem mediaItem = getMediaItem(mediaItemId);
if (mediaItem != null) {
mMANativeHelper.setGeneratePreview(true);
/**
* Remove the media item
*/
mMediaItems.remove(mediaItem);
if (mediaItem instanceof MediaImageItem) {
((MediaImageItem)mediaItem).invalidate();
}
final List<Overlay> overlays = mediaItem.getAllOverlays();
if (overlays.size() > 0) {
for (Overlay overlay : overlays) {
if (overlay instanceof OverlayFrame) {
final OverlayFrame overlayFrame = (OverlayFrame)overlay;
overlayFrame.invalidate();
}
}
}
/**
* Remove the adjacent transitions
*/
removeAdjacentTransitions(mediaItem);
computeTimelineDuration();
}
/**
* If string equals first mediaItem, then
* generate Project thumbnail
*/
if (firstItemString.equals(mediaItemId)) {
generateProjectThumbnail();
}
if (mediaItem instanceof MediaVideoItem) {
/**
* Delete the graph file
*/
((MediaVideoItem)mediaItem).invalidate();
}
return mediaItem;
}
private synchronized MediaItem removeMediaItem(String mediaItemId, boolean flag) {
final String firstItemString = mMediaItems.get(0).getId();
final MediaItem mediaItem = getMediaItem(mediaItemId);
if (mediaItem != null) {
mMANativeHelper.setGeneratePreview(true);
/**
* Remove the media item
*/
mMediaItems.remove(mediaItem);
/**
* Remove the adjacent transitions
*/
removeAdjacentTransitions(mediaItem);
computeTimelineDuration();
}
/**
* If string equals first mediaItem, then
* generate Project thumbail
*/
if (firstItemString.equals(mediaItemId)) {
generateProjectThumbnail();
}
return mediaItem;
}
/*
* {@inheritDoc}
*/
public synchronized Transition removeTransition(String transitionId) {
final Transition transition = getTransition(transitionId);
if (transition == null) {
throw new IllegalStateException("Transition not found: " + transitionId);
}
mMANativeHelper.setGeneratePreview(true);
/**
* Remove the transition references
*/
final MediaItem afterMediaItem = transition.getAfterMediaItem();
if (afterMediaItem != null) {
afterMediaItem.setEndTransition(null);
}
final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
if (beforeMediaItem != null) {
beforeMediaItem.setBeginTransition(null);
}
mTransitions.remove(transition);
transition.invalidate();
computeTimelineDuration();
return transition;
}
/*
* {@inheritDoc}
*/
public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs,
OverlayData overlayData) {
if (surfaceHolder == null) {
throw new IllegalArgumentException("Surface Holder is null");
}
final Surface surface = surfaceHolder.getSurface();
if (surface == null) {
throw new IllegalArgumentException("Surface could not be retrieved from Surface holder");
}
if (surface.isValid() == false) {
throw new IllegalStateException("Surface is not valid");
}
if (timeMs < 0) {
throw new IllegalArgumentException("requested time not correct");
} else if (timeMs > mDurationMs) {
throw new IllegalArgumentException("requested time more than duration");
}
long result = 0;
boolean semAcquireDone = false;
try {
semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS);
if (semAcquireDone == false) {
throw new IllegalStateException("Timeout waiting for semaphore");
}
if (mMANativeHelper == null) {
throw new IllegalStateException("The video editor is not initialized");
}
if (mMediaItems.size() > 0) {
final Rect frame = surfaceHolder.getSurfaceFrame();
result = mMANativeHelper.renderPreviewFrame(surface,
timeMs, frame.width(), frame.height(), overlayData);
} else {
result = 0;
}
} catch (InterruptedException ex) {
Log.w(TAG, "The thread was interrupted", new Throwable());
throw new IllegalStateException("The thread was interrupted");
} finally {
if (semAcquireDone) {
unlock();
}
}
return result;
}
/**
* the project form XML
*/
private void load() throws FileNotFoundException, XmlPullParserException, IOException {
final File file = new File(mProjectPath, PROJECT_FILENAME);
/**
* Load the metadata
*/
final FileInputStream fis = new FileInputStream(file);
try {
final List<String> ignoredMediaItems = new ArrayList<String>();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, "UTF-8");
int eventType = parser.getEventType();
String name;
MediaItem currentMediaItem = null;
Overlay currentOverlay = null;
boolean regenerateProjectThumbnail = false;
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG: {
name = parser.getName();
if (TAG_PROJECT.equals(name)) {
mAspectRatio = Integer.parseInt(parser.getAttributeValue("",
ATTR_ASPECT_RATIO));
final boolean mRegenPCM =
Boolean.parseBoolean(parser.getAttributeValue("",
ATTR_REGENERATE_PCM));
mMANativeHelper.setAudioflag(mRegenPCM);
} else if (TAG_MEDIA_ITEM.equals(name)) {
final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
try {
currentMediaItem = parseMediaItem(parser);
mMediaItems.add(currentMediaItem);
} catch (Exception ex) {
Log.w(TAG, "Cannot load media item: " + mediaItemId, ex);
currentMediaItem = null;
// First media item is invalid, mark for project thumbnail removal
if (mMediaItems.size() == 0) {
regenerateProjectThumbnail = true;
}
// Ignore the media item
ignoredMediaItems.add(mediaItemId);
}
} else if (TAG_TRANSITION.equals(name)) {
try {
final Transition transition = parseTransition(parser,
ignoredMediaItems);
// The transition will be null if the bounding
// media items are ignored
if (transition != null) {
mTransitions.add(transition);
}
} catch (Exception ex) {
Log.w(TAG, "Cannot load transition", ex);
}
} else if (TAG_OVERLAY.equals(name)) {
if (currentMediaItem != null) {
try {
currentOverlay = parseOverlay(parser, currentMediaItem);
currentMediaItem.addOverlay(currentOverlay);
} catch (Exception ex) {
Log.w(TAG, "Cannot load overlay", ex);
}
}
} else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) {
if (currentOverlay != null) {
final int attributesCount = parser.getAttributeCount();
for (int i = 0; i < attributesCount; i++) {
currentOverlay.setUserAttribute(parser.getAttributeName(i),
parser.getAttributeValue(i));
}
}
} else if (TAG_EFFECT.equals(name)) {
if (currentMediaItem != null) {
try {
final Effect effect = parseEffect(parser, currentMediaItem);
currentMediaItem.addEffect(effect);
if (effect instanceof EffectKenBurns) {
final boolean isImageClipGenerated =
Boolean.parseBoolean(parser.getAttributeValue("",
ATTR_IS_IMAGE_CLIP_GENERATED));
if(isImageClipGenerated) {
final String filename = parser.getAttributeValue("",
ATTR_GENERATED_IMAGE_CLIP);
if (new File(filename).exists() == true) {
((MediaImageItem)currentMediaItem).
setGeneratedImageClip(filename);
((MediaImageItem)currentMediaItem).
setRegenerateClip(false);
} else {
((MediaImageItem)currentMediaItem).
setGeneratedImageClip(null);
((MediaImageItem)currentMediaItem).
setRegenerateClip(true);
}
} else {
((MediaImageItem)currentMediaItem).
setGeneratedImageClip(null);
((MediaImageItem)currentMediaItem).
setRegenerateClip(true);
}
}
} catch (Exception ex) {
Log.w(TAG, "Cannot load effect", ex);
}
}
} else if (TAG_AUDIO_TRACK.equals(name)) {
try {
final AudioTrack audioTrack = parseAudioTrack(parser);
addAudioTrack(audioTrack);
} catch (Exception ex) {
Log.w(TAG, "Cannot load audio track", ex);
}
}
break;
}
case XmlPullParser.END_TAG: {
name = parser.getName();
if (TAG_MEDIA_ITEM.equals(name)) {
currentMediaItem = null;
} else if (TAG_OVERLAY.equals(name)) {
currentOverlay = null;
}
break;
}
default: {
break;
}
}
eventType = parser.next();
}
computeTimelineDuration();
// Regenerate project thumbnail
if (regenerateProjectThumbnail) {
generateProjectThumbnail();
regenerateProjectThumbnail = false;
}
} finally {
if (fis != null) {
fis.close();
}
}
}
/**
* Parse the media item
*
* @param parser The parser
* @return The media item
*/
private MediaItem parseMediaItem(XmlPullParser parser) throws IOException {
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 currentMediaItem;
if (MediaImageItem.class.getSimpleName().equals(type)) {
final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
durationMs, renderingMode);
} else if (MediaVideoItem.class.getSimpleName().equals(type)) {
final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
final String audioWaveformFilename = parser.getAttributeValue("",
ATTR_AUDIO_WAVEFORM_FILENAME);
currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
renderingMode, beginMs, endMs, volume, muted, audioWaveformFilename);
final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs, endTimeMs);
final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
((MediaVideoItem)currentMediaItem).setVolume(volumePercent);
} else {
throw new IllegalArgumentException("Unknown media item type: " + type);
}
return currentMediaItem;
}
/**
* Parse the transition
*
* @param parser The parser
* @param ignoredMediaItems The list of ignored media items
*
* @return The transition
*/
private Transition parseTransition(XmlPullParser parser, List<String> ignoredMediaItems) {
final String transitionId = parser.getAttributeValue("", ATTR_ID);
final String type = parser.getAttributeValue("", ATTR_TYPE);
final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
final int behavior = Integer.parseInt(parser.getAttributeValue("", ATTR_BEHAVIOR));
final String beforeMediaItemId = parser.getAttributeValue("", ATTR_BEFORE_MEDIA_ITEM_ID);
final MediaItem beforeMediaItem;
if (beforeMediaItemId != null) {
if (ignoredMediaItems.contains(beforeMediaItemId)) {
// This transition is ignored
return null;
}
beforeMediaItem = getMediaItem(beforeMediaItemId);
} else {
beforeMediaItem = null;
}
final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID);
final MediaItem afterMediaItem;
if (afterMediaItemId != null) {
if (ignoredMediaItems.contains(afterMediaItemId)) {
// This transition is ignored
return null;
}
afterMediaItem = getMediaItem(afterMediaItemId);
} else {
afterMediaItem = null;
}
final Transition transition;
if (TransitionAlpha.class.getSimpleName().equals(type)) {
final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior, maskFilename, blending, invert);
} else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior);
} else if (TransitionSliding.class.getSimpleName().equals(type)) {
final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior, direction);
} else if (TransitionFadeBlack.class.getSimpleName().equals(type)) {
transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior);
} else {
throw new IllegalArgumentException("Invalid transition type: " + type);
}
final boolean isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("",
ATTR_IS_TRANSITION_GENERATED));
if (isTransitionGenerated == true) {
final String transitionFile = parser.getAttributeValue("",
ATTR_GENERATED_TRANSITION_CLIP);
if (new File(transitionFile).exists()) {
transition.setFilename(transitionFile);
} else {
transition.setFilename(null);
}
}
// Use the transition
if (beforeMediaItem != null) {
beforeMediaItem.setBeginTransition(transition);
}
if (afterMediaItem != null) {
afterMediaItem.setEndTransition(transition);
}
return transition;
}
/**
* Parse the overlay
*
* @param parser The parser
* @param mediaItem The media item owner
*
* @return The overlay
*/
private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) {
final String overlayId = parser.getAttributeValue("", ATTR_ID);
final String type = parser.getAttributeValue("", ATTR_TYPE);
final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
final Overlay overlay;
if (OverlayFrame.class.getSimpleName().equals(type)) {
final String filename = parser.getAttributeValue("", ATTR_FILENAME);
overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs);
} else {
throw new IllegalArgumentException("Invalid overlay type: " + type);
}
final String overlayRgbFileName = parser.getAttributeValue("", ATTR_OVERLAY_RGB_FILENAME);
if (overlayRgbFileName != null) {
((OverlayFrame)overlay).setFilename(overlayRgbFileName);
final int overlayFrameWidth = Integer.parseInt(parser.getAttributeValue("",
ATTR_OVERLAY_FRAME_WIDTH));
final int overlayFrameHeight = Integer.parseInt(parser.getAttributeValue("",
ATTR_OVERLAY_FRAME_HEIGHT));
((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth);
((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight);
final int resizedRGBFrameWidth = Integer.parseInt(parser.getAttributeValue("",
ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH));
final int resizedRGBFrameHeight = Integer.parseInt(parser.getAttributeValue("",
ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT));
((OverlayFrame)overlay).setResizedRGBSize(resizedRGBFrameWidth, resizedRGBFrameHeight);
}
return overlay;
}
/**
* Parse the effect
*
* @param parser The parser
* @param mediaItem The media item owner
*
* @return The effect
*/
private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
final String effectId = parser.getAttributeValue("", ATTR_ID);
final String type = parser.getAttributeValue("", ATTR_TYPE);
final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
final Effect effect;
if (EffectColor.class.getSimpleName().equals(type)) {
final int colorEffectType = Integer.parseInt(parser.getAttributeValue("",
ATTR_COLOR_EFFECT_TYPE));
final int color;
if (colorEffectType == EffectColor.TYPE_COLOR
|| colorEffectType == EffectColor.TYPE_GRADIENT) {
color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE));
} else {
color = 0;
}
effect = new EffectColor(mediaItem, effectId, startTimeMs,
durationMs, colorEffectType, color);
} else if (EffectKenBurns.class.getSimpleName().equals(type)) {
final Rect startRect = new Rect(
Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_LEFT)),
Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_TOP)),
Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_RIGHT)),
Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_BOTTOM)));
final Rect endRect = new Rect(
Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_LEFT)),
Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_TOP)),
Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_RIGHT)),
Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_BOTTOM)));
effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect,
startTimeMs, durationMs);
} else {
throw new IllegalArgumentException("Invalid effect type: " + type);
}
return effect;
}
/**
* Parse the audio track
*
* @param parser The parser
*
* @return The audio track
*/
private AudioTrack parseAudioTrack(XmlPullParser parser) throws IOException {
final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
final String filename = parser.getAttributeValue("", ATTR_FILENAME);
final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME));
final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
final boolean duckingEnabled = Boolean.parseBoolean(
parser.getAttributeValue("", ATTR_DUCK_ENABLED));
final int duckThreshold = Integer.parseInt(
parser.getAttributeValue("", ATTR_DUCK_THRESHOLD));
final int duckedTrackVolume = Integer.parseInt(parser.getAttributeValue("",
ATTR_DUCKED_TRACK_VOLUME));
final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
final AudioTrack audioTrack = new AudioTrack(this, audioTrackId,
filename, startTimeMs,
beginMs, endMs, loop,
volume, muted,
duckingEnabled,
duckThreshold,
duckedTrackVolume,
waveformFilename);
return audioTrack;
}
/*
* {@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.attribute("", ATTR_REGENERATE_PCM,
Boolean.toString(mMANativeHelper.getAudioflag()));
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()));
serializer.attribute("", ATTR_MUTED,
Boolean.toString(mvi.isMuted()));
if (mvi.getAudioWaveformFilename() != null) {
serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
mvi.getAudioWaveformFilename());
}
} else if (mediaItem instanceof MediaImageItem) {
serializer.attribute("", ATTR_DURATION,
Long.toString(mediaItem.getTimelineDuration()));
}
final List<Overlay> overlays = mediaItem.getAllOverlays();
if (overlays.size() > 0) {
serializer.startTag("", TAG_OVERLAYS);
for (Overlay overlay : overlays) {
serializer.startTag("", TAG_OVERLAY);
serializer.attribute("", ATTR_ID, overlay.getId());
serializer.attribute("",
ATTR_TYPE, overlay.getClass().getSimpleName());
serializer.attribute("", ATTR_BEGIN_TIME,
Long.toString(overlay.getStartTime()));
serializer.attribute("", ATTR_DURATION,
Long.toString(overlay.getDuration()));
if (overlay instanceof OverlayFrame) {
final OverlayFrame overlayFrame = (OverlayFrame)overlay;
overlayFrame.save(getPath());
if (overlayFrame.getBitmapImageFileName() != null) {
serializer.attribute("", ATTR_FILENAME,
overlayFrame.getBitmapImageFileName());
}
if (overlayFrame.getFilename() != null) {
serializer.attribute("",
ATTR_OVERLAY_RGB_FILENAME,
overlayFrame.getFilename());
serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH,
Integer.toString(overlayFrame.getOverlayFrameWidth()));
serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT,
Integer.toString(overlayFrame.getOverlayFrameHeight()));
serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH,
Integer.toString(overlayFrame.getResizedRGBSizeWidth()));
serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT,
Integer.toString(overlayFrame.getResizedRGBSizeHeight()));
}
}
/**
* Save the user attributes
*/
serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES);
final Map<String, String> userAttributes = overlay.getUserAttributes();
for (String name : userAttributes.keySet()) {
final String value = userAttributes.get(name);
if (value != null) {
serializer.attribute("", name, value);
}
}
serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES);
serializer.endTag("", TAG_OVERLAY);
}
serializer.endTag("", TAG_OVERLAYS);
}
final List<Effect> effects = mediaItem.getAllEffects();
if (effects.size() > 0) {
serializer.startTag("", TAG_EFFECTS);
for (Effect effect : effects) {
serializer.startTag("", TAG_EFFECT);
serializer.attribute("", ATTR_ID, effect.getId());
serializer.attribute("",
ATTR_TYPE, effect.getClass().getSimpleName());
serializer.attribute("", ATTR_BEGIN_TIME,
Long.toString(effect.getStartTime()));
serializer.attribute("", ATTR_DURATION,
Long.toString(effect.getDuration()));
if (effect instanceof EffectColor) {
final EffectColor colorEffect = (EffectColor)effect;
serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
Integer.toString(colorEffect.getType()));
if (colorEffect.getType() == EffectColor.TYPE_COLOR ||
colorEffect.getType() == EffectColor.TYPE_GRADIENT) {
serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
Integer.toString(colorEffect.getColor()));
}
} else if (effect instanceof EffectKenBurns) {
final Rect startRect = ((EffectKenBurns)effect).getStartRect();
serializer.attribute("", ATTR_START_RECT_LEFT,
Integer.toString(startRect.left));
serializer.attribute("", ATTR_START_RECT_TOP,
Integer.toString(startRect.top));
serializer.attribute("", ATTR_START_RECT_RIGHT,
Integer.toString(startRect.right));
serializer.attribute("", ATTR_START_RECT_BOTTOM,
Integer.toString(startRect.bottom));
final Rect endRect = ((EffectKenBurns)effect).getEndRect();
serializer.attribute("", ATTR_END_RECT_LEFT,
Integer.toString(endRect.left));
serializer.attribute("", ATTR_END_RECT_TOP,
Integer.toString(endRect.top));
serializer.attribute("", ATTR_END_RECT_RIGHT,
Integer.toString(endRect.right));
serializer.attribute("", ATTR_END_RECT_BOTTOM,
Integer.toString(endRect.bottom));
final MediaItem mItem = effect.getMediaItem();
if(((MediaImageItem)mItem).getGeneratedImageClip() != null) {
serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
Boolean.toString(true));
serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP,
((MediaImageItem)mItem).getGeneratedImageClip());
} else {
serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
Boolean.toString(false));
}
}
serializer.endTag("", TAG_EFFECT);
}
serializer.endTag("", TAG_EFFECTS);
}
serializer.endTag("", TAG_MEDIA_ITEM);
}
serializer.endTag("", TAG_MEDIA_ITEMS);
serializer.startTag("", TAG_TRANSITIONS);
for (Transition transition : mTransitions) {
serializer.startTag("", TAG_TRANSITION);
serializer.attribute("", ATTR_ID, transition.getId());
serializer.attribute("", ATTR_TYPE, transition.getClass().getSimpleName());
serializer.attribute("", ATTR_DURATION, Long.toString(transition.getDuration()));
serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(transition.getBehavior()));
serializer.attribute("", ATTR_IS_TRANSITION_GENERATED,
Boolean.toString(transition.isGenerated()));
if (transition.isGenerated() == true) {
serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP, transition.mFilename);
}
final MediaItem afterMediaItem = transition.getAfterMediaItem();
if (afterMediaItem != null) {
serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID, afterMediaItem.getId());
}
final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
if (beforeMediaItem != null) {
serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID, beforeMediaItem.getId());
}
if (transition instanceof TransitionSliding) {
serializer.attribute("", ATTR_DIRECTION,
Integer.toString(((TransitionSliding)transition).getDirection()));
} else if (transition instanceof TransitionAlpha) {
TransitionAlpha ta = (TransitionAlpha)transition;
serializer.attribute("", ATTR_BLENDING,
Integer.toString(ta.getBlendingPercent()));
serializer.attribute("", ATTR_INVERT,
Boolean.toString(ta.isInvert()));
if (ta.getMaskFilename() != null) {
serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
}
}
serializer.endTag("", TAG_TRANSITION);
}
serializer.endTag("", TAG_TRANSITIONS);
serializer.startTag("", TAG_AUDIO_TRACKS);
for (AudioTrack at : mAudioTracks) {
serializer.startTag("", TAG_AUDIO_TRACK);
serializer.attribute("", ATTR_ID, at.getId());
serializer.attribute("", ATTR_FILENAME, at.getFilename());
serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime()));
serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime()));
serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime()));
serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume()));
serializer.attribute("", ATTR_DUCK_ENABLED,
Boolean.toString(at.isDuckingEnabled()));
serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME,
Integer.toString(at.getDuckedTrackVolume()));
serializer.attribute("", ATTR_DUCK_THRESHOLD,
Integer.toString(at.getDuckingThreshhold()));
serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
if (at.getAudioWaveformFilename() != null) {
serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
at.getAudioWaveformFilename());
}
serializer.endTag("", TAG_AUDIO_TRACK);
}
serializer.endTag("", TAG_AUDIO_TRACKS);
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();
}
/*
* {@inheritDoc}
*/
public void setAspectRatio(int aspectRatio) {
mAspectRatio = aspectRatio;
/**
* Invalidate all transitions
*/
mMANativeHelper.setGeneratePreview(true);
for (Transition transition : mTransitions) {
transition.invalidate();
}
final Iterator<MediaItem> it = mMediaItems.iterator();
while (it.hasNext()) {
final MediaItem t = it.next();
List<Overlay> overlayList = t.getAllOverlays();
for (Overlay overlay : overlayList) {
((OverlayFrame)overlay).invalidateGeneratedFiles();
}
}
}
/*
* {@inheritDoc}
*/
public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs,
boolean loop, int callbackAfterFrameCount,
PreviewProgressListener listener) {
if (surfaceHolder == null) {
throw new IllegalArgumentException();
}
final Surface surface = surfaceHolder.getSurface();
if (surface == null) {
throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
}
if (surface.isValid() == false) {
throw new IllegalStateException("Surface is not valid");
}
if (listener == null) {
throw new IllegalArgumentException();
}
if (fromMs >= mDurationMs) {
throw new IllegalArgumentException("Requested time not correct");
}
if (fromMs < 0) {
throw new IllegalArgumentException("Requested time not correct");
}
boolean semAcquireDone = false;
if (!mPreviewInProgress) {
try{
semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS);
if (semAcquireDone == false) {
throw new IllegalStateException("Timeout waiting for semaphore");
}
if (mMANativeHelper == null) {
throw new IllegalStateException("The video editor is not initialized");
}
if (mMediaItems.size() > 0) {
mPreviewInProgress = true;
mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions,
mAudioTracks, null);
mMANativeHelper.doPreview(surface, fromMs, toMs, loop,
callbackAfterFrameCount, listener);
}
/**
* Release The lock on complete by calling stopPreview
*/
} catch (InterruptedException ex) {
Log.w(TAG, "The thread was interrupted", new Throwable());
throw new IllegalStateException("The thread was interrupted");
}
} else {
throw new IllegalStateException("Preview already in progress");
}
}
/*
* {@inheritDoc}
*/
public long stopPreview() {
long result = 0;
if (mPreviewInProgress) {
try {
result = mMANativeHelper.stopPreview();
/**
* release on complete by calling stopPreview
*/
} finally {
mPreviewInProgress = false;
unlock();
}
return result;
}
else {
return 0;
}
}
/*
* Remove transitions associated with the specified media item
*
* @param mediaItem The media item
*/
private void removeAdjacentTransitions(MediaItem mediaItem) {
final Transition beginTransition = mediaItem.getBeginTransition();
if (beginTransition != null) {
if (beginTransition.getAfterMediaItem() != null) {
beginTransition.getAfterMediaItem().setEndTransition(null);
}
beginTransition.invalidate();
mTransitions.remove(beginTransition);
}
final Transition endTransition = mediaItem.getEndTransition();
if (endTransition != null) {
if (endTransition.getBeforeMediaItem() != null) {
endTransition.getBeforeMediaItem().setBeginTransition(null);
}
endTransition.invalidate();
mTransitions.remove(endTransition);
}
mediaItem.setBeginTransition(null);
mediaItem.setEndTransition(null);
}
/**
* Remove the transition before this media item
*
* @param index The media item index
*/
private void removeTransitionBefore(int index) {
final MediaItem mediaItem = mMediaItems.get(index);
final Iterator<Transition> it = mTransitions.iterator();
while (it.hasNext()) {
Transition t = it.next();
if (t.getBeforeMediaItem() == mediaItem) {
mMANativeHelper.setGeneratePreview(true);
it.remove();
t.invalidate();
mediaItem.setBeginTransition(null);
if (index > 0) {
mMediaItems.get(index - 1).setEndTransition(null);
}
break;
}
}
}
/**
* Remove the transition after this media item
*
* @param mediaItem The media item
*/
private void removeTransitionAfter(int index) {
final MediaItem mediaItem = mMediaItems.get(index);
final Iterator<Transition> it = mTransitions.iterator();
while (it.hasNext()) {
Transition t = it.next();
if (t.getAfterMediaItem() == mediaItem) {
mMANativeHelper.setGeneratePreview(true);
it.remove();
t.invalidate();
mediaItem.setEndTransition(null);
/**
* Invalidate the reference in the next media item
*/
if (index < mMediaItems.size() - 1) {
mMediaItems.get(index + 1).setBeginTransition(null);
}
break;
}
}
}
/**
* Compute the duration
*/
private void computeTimelineDuration() {
mDurationMs = 0;
final int mediaItemsCount = mMediaItems.size();
for (int i = 0; i < mediaItemsCount; i++) {
final MediaItem mediaItem = mMediaItems.get(i);
mDurationMs += mediaItem.getTimelineDuration();
if (mediaItem.getEndTransition() != null) {
if (i < mediaItemsCount - 1) {
mDurationMs -= mediaItem.getEndTransition().getDuration();
}
}
}
}
/*
* Generate the project thumbnail
*/
private void generateProjectThumbnail() {
/*
* If a thumbnail already exists, then delete it first
*/
if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
(new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
}
/*
* Generate a new thumbnail for the project from first media Item
*/
if (mMediaItems.size() > 0) {
MediaItem mI = mMediaItems.get(0);
/*
* Keep aspect ratio of the image
*/
int height = 480;
int width = mI.getWidth() * height / mI.getHeight();
Bitmap projectBitmap = null;
String filename = mI.getFilename();
if (mI instanceof MediaVideoItem) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(filename);
Bitmap bitmap = retriever.getFrameAtTime();
retriever.release();
retriever = null;
if (bitmap == null) {
String msg = "Thumbnail extraction from " +
filename + " failed";
throw new IllegalArgumentException(msg);
}
// Resize the thumbnail to the target size
projectBitmap =
Bitmap.createScaledBitmap(bitmap, width, height, true);
} else {
try {
projectBitmap = mI.getThumbnail(width, height, 500);
} catch (IllegalArgumentException e) {
String msg = "Project thumbnail extraction from " +
filename + " failed";
throw new IllegalArgumentException(msg);
} catch (IOException e) {
String msg = "IO Error creating project thumbnail";
throw new IllegalArgumentException(msg);
}
}
FileOutputStream stream = null;
try {
stream = new FileOutputStream(mProjectPath + "/" + THUMBNAIL_FILENAME);
projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
stream.flush();
} catch (IOException e) {
throw new IllegalArgumentException ("Error creating project thumbnail");
} finally {
IoUtils.closeQuietly(stream);
projectBitmap.recycle();
}
}
}
/**
* Clears the preview surface
*
* @param surfaceHolder SurfaceHolder where the preview is rendered
* and needs to be cleared.
*/
public void clearSurface(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalArgumentException("Invalid surface holder");
}
final Surface surface = surfaceHolder.getSurface();
if (surface == null) {
throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
}
if (surface.isValid() == false) {
throw new IllegalStateException("Surface is not valid");
}
if (mMANativeHelper != null) {
mMANativeHelper.clearPreviewSurface(surface);
} else {
Log.w(TAG, "Native helper was not ready!");
}
}
/**
* Grab the semaphore which arbitrates access to the editor
*
* @throws InterruptedException
*/
private void lock() throws InterruptedException {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "lock: grabbing semaphore", new Throwable());
}
mLock.acquire();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "lock: grabbed semaphore");
}
}
/**
* Tries to grab the semaphore with a specified time out which arbitrates access to the editor
*
* @param timeoutMs time out in ms.
*
* @return true if the semaphore is acquired, false otherwise
* @throws InterruptedException
*/
private boolean lock(long timeoutMs) throws InterruptedException {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "lock: grabbing semaphore with timeout " + timeoutMs, new Throwable());
}
boolean acquireSem = mLock.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "lock: grabbed semaphore status " + acquireSem);
}
return acquireSem;
}
/**
* Release the semaphore which arbitrates access to the editor
*/
private void unlock() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "unlock: releasing semaphore");
}
mLock.release();
}
/**
* Dumps the heap memory usage information to file
*/
private static void dumpHeap (String filename) throws Exception {
/* Cleanup as much as possible before dump
*/
System.gc();
System.runFinalization();
Thread.sleep(1000);
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
String extDir =
Environment.getExternalStorageDirectory().toString();
/* If dump file already exists, then delete it first
*/
if ((new File(extDir + "/" + filename + ".dump")).exists()) {
(new File(extDir + "/" + filename + ".dump")).delete();
}
/* Dump native heap
*/
FileOutputStream ost =
new FileOutputStream(extDir + "/" + filename + ".dump");
Debug.dumpNativeHeap(ost.getFD());
ost.close();
}
}
}