diff options
Diffstat (limited to 'src/com/android/camera/EffectsRecorder.java')
-rw-r--r-- | src/com/android/camera/EffectsRecorder.java | 1240 |
1 files changed, 0 insertions, 1240 deletions
diff --git a/src/com/android/camera/EffectsRecorder.java b/src/com/android/camera/EffectsRecorder.java deleted file mode 100644 index 151441ef9..000000000 --- a/src/com/android/camera/EffectsRecorder.java +++ /dev/null @@ -1,1240 +0,0 @@ -/* - * Copyright (C) 2011 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 com.android.camera; - -import java.io.FileDescriptor; -import java.io.Serializable; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.media.CamcorderProfile; -import android.media.MediaRecorder; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import com.android.camera.util.ApiHelper; -import com.android.camera.util.CameraUtil; -import com.android.camera2.R; - - -/** - * Encapsulates the mobile filter framework components needed to record video - * with effects applied. Modeled after MediaRecorder. - */ -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture -public class EffectsRecorder { - private static final String TAG = "EffectsRecorder"; - - private static Class<?> sClassFilter; - private static Method sFilterIsAvailable; - private static EffectsRecorder sEffectsRecorder; - // The index of the current effects recorder. - private static int sEffectsRecorderIndex; - - private static boolean sReflectionInited = false; - - private static Class<?> sClsLearningDoneListener; - private static Class<?> sClsOnRunnerDoneListener; - private static Class<?> sClsOnRecordingDoneListener; - private static Class<?> sClsSurfaceTextureSourceListener; - - private static Method sFilterSetInputValue; - - private static Constructor<?> sCtPoint; - private static Constructor<?> sCtQuad; - - private static Method sLearningDoneListenerOnLearningDone; - - private static Method sObjectEquals; - private static Method sObjectToString; - - private static Class<?> sClsGraphRunner; - private static Method sGraphRunnerGetGraph; - private static Method sGraphRunnerSetDoneCallback; - private static Method sGraphRunnerRun; - private static Method sGraphRunnerGetError; - private static Method sGraphRunnerStop; - - private static Method sFilterGraphGetFilter; - private static Method sFilterGraphTearDown; - - private static Method sOnRunnerDoneListenerOnRunnerDone; - - private static Class<?> sClsGraphEnvironment; - private static Constructor<?> sCtGraphEnvironment; - private static Method sGraphEnvironmentCreateGLEnvironment; - private static Method sGraphEnvironmentGetRunner; - private static Method sGraphEnvironmentAddReferences; - private static Method sGraphEnvironmentLoadGraph; - private static Method sGraphEnvironmentGetContext; - - private static Method sFilterContextGetGLEnvironment; - private static Method sGLEnvironmentIsActive; - private static Method sGLEnvironmentActivate; - private static Method sGLEnvironmentDeactivate; - private static Method sSurfaceTextureTargetDisconnect; - private static Method sOnRecordingDoneListenerOnRecordingDone; - private static Method sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady; - - private Object mLearningDoneListener; - private Object mRunnerDoneCallback; - private Object mSourceReadyCallback; - // A callback to finalize the media after the recording is done. - private Object mRecordingDoneListener; - - static { - try { - sClassFilter = Class.forName("android.filterfw.core.Filter"); - sFilterIsAvailable = sClassFilter.getMethod("isAvailable", - String.class); - } catch (ClassNotFoundException ex) { - Log.v(TAG, "Can't find the class android.filterfw.core.Filter"); - } catch (NoSuchMethodException e) { - Log.v(TAG, "Can't find the method Filter.isAvailable"); - } - } - - public static final int EFFECT_NONE = 0; - public static final int EFFECT_GOOFY_FACE = 1; - public static final int EFFECT_BACKDROPPER = 2; - - public static final int EFFECT_GF_SQUEEZE = 0; - public static final int EFFECT_GF_BIG_EYES = 1; - public static final int EFFECT_GF_BIG_MOUTH = 2; - public static final int EFFECT_GF_SMALL_MOUTH = 3; - public static final int EFFECT_GF_BIG_NOSE = 4; - public static final int EFFECT_GF_SMALL_EYES = 5; - public static final int NUM_OF_GF_EFFECTS = EFFECT_GF_SMALL_EYES + 1; - - public static final int EFFECT_MSG_STARTED_LEARNING = 0; - public static final int EFFECT_MSG_DONE_LEARNING = 1; - public static final int EFFECT_MSG_SWITCHING_EFFECT = 2; - public static final int EFFECT_MSG_EFFECTS_STOPPED = 3; - public static final int EFFECT_MSG_RECORDING_DONE = 4; - public static final int EFFECT_MSG_PREVIEW_RUNNING = 5; - - private Context mContext; - private Handler mHandler; - - private CameraManager.CameraProxy mCameraDevice; - private CamcorderProfile mProfile; - private double mCaptureRate = 0; - private SurfaceTexture mPreviewSurfaceTexture; - private int mPreviewWidth; - private int mPreviewHeight; - private MediaRecorder.OnInfoListener mInfoListener; - private MediaRecorder.OnErrorListener mErrorListener; - - private String mOutputFile; - private FileDescriptor mFd; - private int mOrientationHint = 0; - private long mMaxFileSize = 0; - private int mMaxDurationMs = 0; - private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; - private int mCameraDisplayOrientation; - - private int mEffect = EFFECT_NONE; - private int mCurrentEffect = EFFECT_NONE; - private EffectsListener mEffectsListener; - - private Object mEffectParameter; - - private Object mGraphEnv; - private int mGraphId; - private Object mRunner = null; - private Object mOldRunner = null; - - private SurfaceTexture mTextureSource; - - private static final int STATE_CONFIGURE = 0; - private static final int STATE_WAITING_FOR_SURFACE = 1; - private static final int STATE_STARTING_PREVIEW = 2; - private static final int STATE_PREVIEW = 3; - private static final int STATE_RECORD = 4; - private static final int STATE_RELEASED = 5; - private int mState = STATE_CONFIGURE; - - private boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); - private SoundClips.Player mSoundPlayer; - - /** Determine if a given effect is supported at runtime - * Some effects require libraries not available on all devices - */ - public static boolean isEffectSupported(int effectId) { - if (sFilterIsAvailable == null) return false; - - try { - switch (effectId) { - case EFFECT_GOOFY_FACE: - return (Boolean) sFilterIsAvailable.invoke(null, - "com.google.android.filterpacks.facedetect.GoofyRenderFilter"); - case EFFECT_BACKDROPPER: - return (Boolean) sFilterIsAvailable.invoke(null, - "android.filterpacks.videoproc.BackDropperFilter"); - default: - return false; - } - } catch (Exception ex) { - Log.e(TAG, "Fail to check filter", ex); - } - return false; - } - - public EffectsRecorder(Context context) { - if (mLogVerbose) Log.v(TAG, "EffectsRecorder created (" + this + ")"); - - if (!sReflectionInited) { - try { - sFilterSetInputValue = sClassFilter.getMethod("setInputValue", - new Class[] {String.class, Object.class}); - - Class<?> clsPoint = Class.forName("android.filterfw.geometry.Point"); - sCtPoint = clsPoint.getConstructor(new Class[] {float.class, - float.class}); - - Class<?> clsQuad = Class.forName("android.filterfw.geometry.Quad"); - sCtQuad = clsQuad.getConstructor(new Class[] {clsPoint, clsPoint, - clsPoint, clsPoint}); - - Class<?> clsBackDropperFilter = Class.forName( - "android.filterpacks.videoproc.BackDropperFilter"); - sClsLearningDoneListener = Class.forName( - "android.filterpacks.videoproc.BackDropperFilter$LearningDoneListener"); - sLearningDoneListenerOnLearningDone = sClsLearningDoneListener - .getMethod("onLearningDone", new Class[] {clsBackDropperFilter}); - - sObjectEquals = Object.class.getMethod("equals", new Class[] {Object.class}); - sObjectToString = Object.class.getMethod("toString"); - - sClsOnRunnerDoneListener = Class.forName( - "android.filterfw.core.GraphRunner$OnRunnerDoneListener"); - sOnRunnerDoneListenerOnRunnerDone = sClsOnRunnerDoneListener.getMethod( - "onRunnerDone", new Class[] {int.class}); - - sClsGraphRunner = Class.forName("android.filterfw.core.GraphRunner"); - sGraphRunnerGetGraph = sClsGraphRunner.getMethod("getGraph"); - sGraphRunnerSetDoneCallback = sClsGraphRunner.getMethod( - "setDoneCallback", new Class[] {sClsOnRunnerDoneListener}); - sGraphRunnerRun = sClsGraphRunner.getMethod("run"); - sGraphRunnerGetError = sClsGraphRunner.getMethod("getError"); - sGraphRunnerStop = sClsGraphRunner.getMethod("stop"); - - Class<?> clsFilterContext = Class.forName("android.filterfw.core.FilterContext"); - sFilterContextGetGLEnvironment = clsFilterContext.getMethod( - "getGLEnvironment"); - - Class<?> clsFilterGraph = Class.forName("android.filterfw.core.FilterGraph"); - sFilterGraphGetFilter = clsFilterGraph.getMethod("getFilter", - new Class[] {String.class}); - sFilterGraphTearDown = clsFilterGraph.getMethod("tearDown", - new Class[] {clsFilterContext}); - - sClsGraphEnvironment = Class.forName("android.filterfw.GraphEnvironment"); - sCtGraphEnvironment = sClsGraphEnvironment.getConstructor(); - sGraphEnvironmentCreateGLEnvironment = sClsGraphEnvironment.getMethod( - "createGLEnvironment"); - sGraphEnvironmentGetRunner = sClsGraphEnvironment.getMethod( - "getRunner", new Class[] {int.class, int.class}); - sGraphEnvironmentAddReferences = sClsGraphEnvironment.getMethod( - "addReferences", new Class[] {Object[].class}); - sGraphEnvironmentLoadGraph = sClsGraphEnvironment.getMethod( - "loadGraph", new Class[] {Context.class, int.class}); - sGraphEnvironmentGetContext = sClsGraphEnvironment.getMethod( - "getContext"); - - Class<?> clsGLEnvironment = Class.forName("android.filterfw.core.GLEnvironment"); - sGLEnvironmentIsActive = clsGLEnvironment.getMethod("isActive"); - sGLEnvironmentActivate = clsGLEnvironment.getMethod("activate"); - sGLEnvironmentDeactivate = clsGLEnvironment.getMethod("deactivate"); - - Class<?> clsSurfaceTextureTarget = Class.forName( - "android.filterpacks.videosrc.SurfaceTextureTarget"); - sSurfaceTextureTargetDisconnect = clsSurfaceTextureTarget.getMethod( - "disconnect", new Class[] {clsFilterContext}); - - sClsOnRecordingDoneListener = Class.forName( - "android.filterpacks.videosink.MediaEncoderFilter$OnRecordingDoneListener"); - sOnRecordingDoneListenerOnRecordingDone = - sClsOnRecordingDoneListener.getMethod("onRecordingDone"); - - sClsSurfaceTextureSourceListener = Class.forName( - "android.filterpacks.videosrc.SurfaceTextureSource$SurfaceTextureSourceListener"); - sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady = - sClsSurfaceTextureSourceListener.getMethod( - "onSurfaceTextureSourceReady", - new Class[] {SurfaceTexture.class}); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - - sReflectionInited = true; - } - - sEffectsRecorderIndex++; - Log.v(TAG, "Current effects recorder index is " + sEffectsRecorderIndex); - sEffectsRecorder = this; - SerializableInvocationHandler sih = new SerializableInvocationHandler( - sEffectsRecorderIndex); - mLearningDoneListener = Proxy.newProxyInstance( - sClsLearningDoneListener.getClassLoader(), - new Class[] {sClsLearningDoneListener}, sih); - mRunnerDoneCallback = Proxy.newProxyInstance( - sClsOnRunnerDoneListener.getClassLoader(), - new Class[] {sClsOnRunnerDoneListener}, sih); - mSourceReadyCallback = Proxy.newProxyInstance( - sClsSurfaceTextureSourceListener.getClassLoader(), - new Class[] {sClsSurfaceTextureSourceListener}, sih); - mRecordingDoneListener = Proxy.newProxyInstance( - sClsOnRecordingDoneListener.getClassLoader(), - new Class[] {sClsOnRecordingDoneListener}, sih); - - mContext = context; - mHandler = new Handler(Looper.getMainLooper()); - mSoundPlayer = SoundClips.getPlayer(context); - } - - public synchronized void setCamera(CameraManager.CameraProxy cameraDevice) { - switch (mState) { - case STATE_PREVIEW: - throw new RuntimeException("setCamera cannot be called while previewing!"); - case STATE_RECORD: - throw new RuntimeException("setCamera cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException("setCamera called on an already released recorder!"); - default: - break; - } - - mCameraDevice = cameraDevice; - } - - public void setProfile(CamcorderProfile profile) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setProfile cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException("setProfile called on an already released recorder!"); - default: - break; - } - mProfile = profile; - } - - public void setOutputFile(String outputFile) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setOutputFile cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException("setOutputFile called on an already released recorder!"); - default: - break; - } - - mOutputFile = outputFile; - mFd = null; - } - - public void setOutputFile(FileDescriptor fd) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setOutputFile cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException("setOutputFile called on an already released recorder!"); - default: - break; - } - - mOutputFile = null; - mFd = fd; - } - - /** - * Sets the maximum filesize (in bytes) of the recording session. - * This will be passed on to the MediaEncoderFilter and then to the - * MediaRecorder ultimately. If zero or negative, the MediaRecorder will - * disable the limit - */ - public synchronized void setMaxFileSize(long maxFileSize) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setMaxFileSize cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException( - "setMaxFileSize called on an already released recorder!"); - default: - break; - } - mMaxFileSize = maxFileSize; - } - - /** - * Sets the maximum recording duration (in ms) for the next recording session - * Setting it to zero (the default) disables the limit. - */ - public synchronized void setMaxDuration(int maxDurationMs) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setMaxDuration cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException( - "setMaxDuration called on an already released recorder!"); - default: - break; - } - mMaxDurationMs = maxDurationMs; - } - - - public void setCaptureRate(double fps) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setCaptureRate cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException( - "setCaptureRate called on an already released recorder!"); - default: - break; - } - - if (mLogVerbose) Log.v(TAG, "Setting time lapse capture rate to " + fps + " fps"); - mCaptureRate = fps; - } - - public void setPreviewSurfaceTexture(SurfaceTexture previewSurfaceTexture, - int previewWidth, - int previewHeight) { - if (mLogVerbose) Log.v(TAG, "setPreviewSurfaceTexture(" + this + ")"); - switch (mState) { - case STATE_RECORD: - throw new RuntimeException( - "setPreviewSurfaceTexture cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException( - "setPreviewSurfaceTexture called on an already released recorder!"); - default: - break; - } - - mPreviewSurfaceTexture = previewSurfaceTexture; - mPreviewWidth = previewWidth; - mPreviewHeight = previewHeight; - - switch (mState) { - case STATE_WAITING_FOR_SURFACE: - startPreview(); - break; - case STATE_STARTING_PREVIEW: - case STATE_PREVIEW: - initializeEffect(true); - break; - } - } - - public void setEffect(int effect, Object effectParameter) { - if (mLogVerbose) Log.v(TAG, - "setEffect: effect ID " + effect + - ", parameter " + effectParameter.toString()); - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setEffect cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException("setEffect called on an already released recorder!"); - default: - break; - } - - mEffect = effect; - mEffectParameter = effectParameter; - - if (mState == STATE_PREVIEW || - mState == STATE_STARTING_PREVIEW) { - initializeEffect(false); - } - } - - public interface EffectsListener { - public void onEffectsUpdate(int effectId, int effectMsg); - public void onEffectsError(Exception exception, String filePath); - } - - public void setEffectsListener(EffectsListener listener) { - mEffectsListener = listener; - } - - private void setFaceDetectOrientation() { - if (mCurrentEffect == EFFECT_GOOFY_FACE) { - Object rotateFilter = getGraphFilter(mRunner, "rotate"); - Object metaRotateFilter = getGraphFilter(mRunner, "metarotate"); - setInputValue(rotateFilter, "rotation", mOrientationHint); - int reverseDegrees = (360 - mOrientationHint) % 360; - setInputValue(metaRotateFilter, "rotation", reverseDegrees); - } - } - - private void setRecordingOrientation() { - if (mState != STATE_RECORD && mRunner != null) { - Object bl = newInstance(sCtPoint, new Object[] {0, 0}); - Object br = newInstance(sCtPoint, new Object[] {1, 0}); - Object tl = newInstance(sCtPoint, new Object[] {0, 1}); - Object tr = newInstance(sCtPoint, new Object[] {1, 1}); - Object recordingRegion; - if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) { - // The back camera is not mirrored, so use a identity transform - recordingRegion = newInstance(sCtQuad, new Object[] {bl, br, tl, tr}); - } else { - // Recording region needs to be tweaked for front cameras, since they - // mirror their preview - if (mOrientationHint == 0 || mOrientationHint == 180) { - // Horizontal flip in landscape - recordingRegion = newInstance(sCtQuad, new Object[] {br, bl, tr, tl}); - } else { - // Horizontal flip in portrait - recordingRegion = newInstance(sCtQuad, new Object[] {tl, tr, bl, br}); - } - } - Object recorder = getGraphFilter(mRunner, "recorder"); - setInputValue(recorder, "inputRegion", recordingRegion); - } - } - public void setOrientationHint(int degrees) { - switch (mState) { - case STATE_RELEASED: - throw new RuntimeException( - "setOrientationHint called on an already released recorder!"); - default: - break; - } - if (mLogVerbose) Log.v(TAG, "Setting orientation hint to: " + degrees); - mOrientationHint = degrees; - setFaceDetectOrientation(); - setRecordingOrientation(); - } - - public void setCameraDisplayOrientation(int orientation) { - if (mState != STATE_CONFIGURE) { - throw new RuntimeException( - "setCameraDisplayOrientation called after configuration!"); - } - mCameraDisplayOrientation = orientation; - } - - public void setCameraFacing(int facing) { - switch (mState) { - case STATE_RELEASED: - throw new RuntimeException( - "setCameraFacing called on alrady released recorder!"); - default: - break; - } - mCameraFacing = facing; - setRecordingOrientation(); - } - - public void setOnInfoListener(MediaRecorder.OnInfoListener infoListener) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setInfoListener cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException( - "setInfoListener called on an already released recorder!"); - default: - break; - } - mInfoListener = infoListener; - } - - public void setOnErrorListener(MediaRecorder.OnErrorListener errorListener) { - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("setErrorListener cannot be called while recording!"); - case STATE_RELEASED: - throw new RuntimeException( - "setErrorListener called on an already released recorder!"); - default: - break; - } - mErrorListener = errorListener; - } - - private void initializeFilterFramework() { - mGraphEnv = newInstance(sCtGraphEnvironment); - invoke(mGraphEnv, sGraphEnvironmentCreateGLEnvironment); - - int videoFrameWidth = mProfile.videoFrameWidth; - int videoFrameHeight = mProfile.videoFrameHeight; - if (mCameraDisplayOrientation == 90 || mCameraDisplayOrientation == 270) { - int tmp = videoFrameWidth; - videoFrameWidth = videoFrameHeight; - videoFrameHeight = tmp; - } - - invoke(mGraphEnv, sGraphEnvironmentAddReferences, - new Object[] {new Object[] { - "textureSourceCallback", mSourceReadyCallback, - "recordingWidth", videoFrameWidth, - "recordingHeight", videoFrameHeight, - "recordingProfile", mProfile, - "learningDoneListener", mLearningDoneListener, - "recordingDoneListener", mRecordingDoneListener}}); - mRunner = null; - mGraphId = -1; - mCurrentEffect = EFFECT_NONE; - } - - private synchronized void initializeEffect(boolean forceReset) { - if (forceReset || - mCurrentEffect != mEffect || - mCurrentEffect == EFFECT_BACKDROPPER) { - - invoke(mGraphEnv, sGraphEnvironmentAddReferences, - new Object[] {new Object[] { - "previewSurfaceTexture", mPreviewSurfaceTexture, - "previewWidth", mPreviewWidth, - "previewHeight", mPreviewHeight, - "orientation", mOrientationHint}}); - if (mState == STATE_PREVIEW || - mState == STATE_STARTING_PREVIEW) { - // Switching effects while running. Inform video camera. - sendMessage(mCurrentEffect, EFFECT_MSG_SWITCHING_EFFECT); - } - - switch (mEffect) { - case EFFECT_GOOFY_FACE: - mGraphId = (Integer) invoke(mGraphEnv, - sGraphEnvironmentLoadGraph, - new Object[] {mContext, R.raw.goofy_face}); - break; - case EFFECT_BACKDROPPER: - sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING); - mGraphId = (Integer) invoke(mGraphEnv, - sGraphEnvironmentLoadGraph, - new Object[] {mContext, R.raw.backdropper}); - break; - default: - throw new RuntimeException("Unknown effect ID" + mEffect + "!"); - } - mCurrentEffect = mEffect; - - mOldRunner = mRunner; - mRunner = invoke(mGraphEnv, sGraphEnvironmentGetRunner, - new Object[] {mGraphId, - getConstant(sClsGraphEnvironment, "MODE_ASYNCHRONOUS")}); - invoke(mRunner, sGraphRunnerSetDoneCallback, new Object[] {mRunnerDoneCallback}); - if (mLogVerbose) { - Log.v(TAG, "New runner: " + mRunner - + ". Old runner: " + mOldRunner); - } - if (mState == STATE_PREVIEW || - mState == STATE_STARTING_PREVIEW) { - // Switching effects while running. Stop existing runner. - // The stop callback will take care of starting new runner. - mCameraDevice.stopPreview(); - mCameraDevice.setPreviewTexture(null); - invoke(mOldRunner, sGraphRunnerStop); - } - } - - switch (mCurrentEffect) { - case EFFECT_GOOFY_FACE: - tryEnableVideoStabilization(true); - Object goofyFilter = getGraphFilter(mRunner, "goofyrenderer"); - setInputValue(goofyFilter, "currentEffect", - ((Integer) mEffectParameter).intValue()); - break; - case EFFECT_BACKDROPPER: - tryEnableVideoStabilization(false); - Object backgroundSrc = getGraphFilter(mRunner, "background"); - if (ApiHelper.HAS_EFFECTS_RECORDING_CONTEXT_INPUT) { - // Set the context first before setting sourceUrl to - // guarantee the content URI get resolved properly. - setInputValue(backgroundSrc, "context", mContext); - } - setInputValue(backgroundSrc, "sourceUrl", mEffectParameter); - // For front camera, the background video needs to be mirrored in the - // backdropper filter - if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - Object replacer = getGraphFilter(mRunner, "replacer"); - setInputValue(replacer, "mirrorBg", true); - if (mLogVerbose) Log.v(TAG, "Setting the background to be mirrored"); - } - break; - default: - break; - } - setFaceDetectOrientation(); - setRecordingOrientation(); - } - - public synchronized void startPreview() { - if (mLogVerbose) Log.v(TAG, "Starting preview (" + this + ")"); - - switch (mState) { - case STATE_STARTING_PREVIEW: - case STATE_PREVIEW: - // Already running preview - Log.w(TAG, "startPreview called when already running preview"); - return; - case STATE_RECORD: - throw new RuntimeException("Cannot start preview when already recording!"); - case STATE_RELEASED: - throw new RuntimeException("setEffect called on an already released recorder!"); - default: - break; - } - - if (mEffect == EFFECT_NONE) { - throw new RuntimeException("No effect selected!"); - } - if (mEffectParameter == null) { - throw new RuntimeException("No effect parameter provided!"); - } - if (mProfile == null) { - throw new RuntimeException("No recording profile provided!"); - } - if (mPreviewSurfaceTexture == null) { - if (mLogVerbose) Log.v(TAG, "Passed a null surface; waiting for valid one"); - mState = STATE_WAITING_FOR_SURFACE; - return; - } - if (mCameraDevice == null) { - throw new RuntimeException("No camera to record from!"); - } - - if (mLogVerbose) Log.v(TAG, "Initializing filter framework and running the graph."); - initializeFilterFramework(); - - initializeEffect(true); - - mState = STATE_STARTING_PREVIEW; - invoke(mRunner, sGraphRunnerRun); - // Rest of preview startup handled in mSourceReadyCallback - } - - private Object invokeObjectEquals(Object proxy, Object[] args) { - return Boolean.valueOf(proxy == args[0]); - } - - private Object invokeObjectToString() { - return "Proxy-" + toString(); - } - - private void invokeOnLearningDone() { - if (mLogVerbose) Log.v(TAG, "Learning done callback triggered"); - // Called in a processing thread, so have to post message back to UI - // thread - sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_DONE_LEARNING); - enable3ALocks(true); - } - - private void invokeOnRunnerDone(Object[] args) { - int runnerDoneResult = (Integer) args[0]; - synchronized (EffectsRecorder.this) { - if (mLogVerbose) { - Log.v(TAG, - "Graph runner done (" + EffectsRecorder.this - + ", mRunner " + mRunner - + ", mOldRunner " + mOldRunner + ")"); - } - if (runnerDoneResult == - (Integer) getConstant(sClsGraphRunner, "RESULT_ERROR")) { - // Handle error case - Log.e(TAG, "Error running filter graph!"); - Exception e = null; - if (mRunner != null) { - e = (Exception) invoke(mRunner, sGraphRunnerGetError); - } else if (mOldRunner != null) { - e = (Exception) invoke(mOldRunner, sGraphRunnerGetError); - } - raiseError(e); - } - if (mOldRunner != null) { - // Tear down old graph if available - if (mLogVerbose) Log.v(TAG, "Tearing down old graph."); - Object glEnv = getContextGLEnvironment(mGraphEnv); - if (glEnv != null && !(Boolean) invoke(glEnv, sGLEnvironmentIsActive)) { - invoke(glEnv, sGLEnvironmentActivate); - } - getGraphTearDown(mOldRunner, - invoke(mGraphEnv, sGraphEnvironmentGetContext)); - if (glEnv != null && (Boolean) invoke(glEnv, sGLEnvironmentIsActive)) { - invoke(glEnv, sGLEnvironmentDeactivate); - } - mOldRunner = null; - } - if (mState == STATE_PREVIEW || - mState == STATE_STARTING_PREVIEW) { - // Switching effects, start up the new runner - if (mLogVerbose) { - Log.v(TAG, "Previous effect halted. Running graph again. state: " - + mState); - } - tryEnable3ALocks(false); - // In case of an error, the graph restarts from beginning and in case - // of the BACKDROPPER effect, the learner re-learns the background. - // Hence, we need to show the learning dialogue to the user - // to avoid recording before the learning is done. Else, the user - // could start recording before the learning is done and the new - // background comes up later leading to an end result video - // with a heterogeneous background. - // For BACKDROPPER effect, this path is also executed sometimes at - // the end of a normal recording session. In such a case, the graph - // does not restart and hence the learner does not re-learn. So we - // do not want to show the learning dialogue then. - if (runnerDoneResult == (Integer) getConstant( - sClsGraphRunner, "RESULT_ERROR") - && mCurrentEffect == EFFECT_BACKDROPPER) { - sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING); - } - invoke(mRunner, sGraphRunnerRun); - } else if (mState != STATE_RELEASED) { - // Shutting down effects - if (mLogVerbose) Log.v(TAG, "Runner halted, restoring direct preview"); - tryEnable3ALocks(false); - sendMessage(EFFECT_NONE, EFFECT_MSG_EFFECTS_STOPPED); - } else { - // STATE_RELEASED - camera will be/has been released as well, do nothing. - } - } - } - - private void invokeOnSurfaceTextureSourceReady(Object[] args) { - SurfaceTexture source = (SurfaceTexture) args[0]; - if (mLogVerbose) Log.v(TAG, "SurfaceTexture ready callback received"); - synchronized (EffectsRecorder.this) { - mTextureSource = source; - - if (mState == STATE_CONFIGURE) { - // Stop preview happened while the runner was doing startup tasks - // Since we haven't started anything up, don't do anything - // Rest of cleanup will happen in onRunnerDone - if (mLogVerbose) Log.v(TAG, "Ready callback: Already stopped, skipping."); - return; - } - if (mState == STATE_RELEASED) { - // EffectsRecorder has been released, so don't touch the camera device - // or anything else - if (mLogVerbose) Log.v(TAG, "Ready callback: Already released, skipping."); - return; - } - if (source == null) { - if (mLogVerbose) { - Log.v(TAG, "Ready callback: source null! Looks like graph was closed!"); - } - if (mState == STATE_PREVIEW || - mState == STATE_STARTING_PREVIEW || - mState == STATE_RECORD) { - // A null source here means the graph is shutting down - // unexpectedly, so we need to turn off preview before - // the surface texture goes away. - if (mLogVerbose) { - Log.v(TAG, "Ready callback: State: " + mState - + ". stopCameraPreview"); - } - - stopCameraPreview(); - } - return; - } - - // Lock AE/AWB to reduce transition flicker - tryEnable3ALocks(true); - - mCameraDevice.stopPreview(); - if (mLogVerbose) Log.v(TAG, "Runner active, connecting effects preview"); - mCameraDevice.setPreviewTexture(mTextureSource); - - mCameraDevice.startPreview(); - - // Unlock AE/AWB after preview started - tryEnable3ALocks(false); - - mState = STATE_PREVIEW; - - if (mLogVerbose) Log.v(TAG, "Start preview/effect switch complete"); - - // Sending a message to listener that preview is complete - sendMessage(mCurrentEffect, EFFECT_MSG_PREVIEW_RUNNING); - } - } - - private void invokeOnRecordingDone() { - // Forward the callback to the VideoModule object (as an asynchronous event). - if (mLogVerbose) Log.v(TAG, "Recording done callback triggered"); - sendMessage(EFFECT_NONE, EFFECT_MSG_RECORDING_DONE); - } - - public synchronized void startRecording() { - if (mLogVerbose) Log.v(TAG, "Starting recording (" + this + ")"); - - switch (mState) { - case STATE_RECORD: - throw new RuntimeException("Already recording, cannot begin anew!"); - case STATE_RELEASED: - throw new RuntimeException( - "startRecording called on an already released recorder!"); - default: - break; - } - - if ((mOutputFile == null) && (mFd == null)) { - throw new RuntimeException("No output file name or descriptor provided!"); - } - - if (mState == STATE_CONFIGURE) { - startPreview(); - } - - Object recorder = getGraphFilter(mRunner, "recorder"); - if (mFd != null) { - setInputValue(recorder, "outputFileDescriptor", mFd); - } else { - setInputValue(recorder, "outputFile", mOutputFile); - } - // It is ok to set the audiosource without checking for timelapse here - // since that check will be done in the MediaEncoderFilter itself - setInputValue(recorder, "audioSource", MediaRecorder.AudioSource.CAMCORDER); - setInputValue(recorder, "recordingProfile", mProfile); - setInputValue(recorder, "orientationHint", mOrientationHint); - // Important to set the timelapseinterval to 0 if the capture rate is not >0 - // since the recorder does not get created every time the recording starts. - // The recorder infers whether the capture is timelapsed based on the value of - // this interval - boolean captureTimeLapse = mCaptureRate > 0; - if (captureTimeLapse) { - double timeBetweenFrameCapture = 1 / mCaptureRate; - setInputValue(recorder, "timelapseRecordingIntervalUs", - (long) (1000000 * timeBetweenFrameCapture)); - - } else { - setInputValue(recorder, "timelapseRecordingIntervalUs", 0L); - } - - if (mInfoListener != null) { - setInputValue(recorder, "infoListener", mInfoListener); - } - if (mErrorListener != null) { - setInputValue(recorder, "errorListener", mErrorListener); - } - setInputValue(recorder, "maxFileSize", mMaxFileSize); - setInputValue(recorder, "maxDurationMs", mMaxDurationMs); - setInputValue(recorder, "recording", true); - mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING); - mState = STATE_RECORD; - } - - public synchronized void stopRecording() { - if (mLogVerbose) Log.v(TAG, "Stop recording (" + this + ")"); - - switch (mState) { - case STATE_CONFIGURE: - case STATE_STARTING_PREVIEW: - case STATE_PREVIEW: - Log.w(TAG, "StopRecording called when recording not active!"); - return; - case STATE_RELEASED: - throw new RuntimeException("stopRecording called on released EffectsRecorder!"); - default: - break; - } - Object recorder = getGraphFilter(mRunner, "recorder"); - setInputValue(recorder, "recording", false); - mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING); - mState = STATE_PREVIEW; - } - - // Called to tell the filter graph that the display surfacetexture is not valid anymore. - // So the filter graph should not hold any reference to the surface created with that. - public synchronized void disconnectDisplay() { - if (mLogVerbose) Log.v(TAG, "Disconnecting the graph from the " + - "SurfaceTexture"); - Object display = getGraphFilter(mRunner, "display"); - invoke(display, sSurfaceTextureTargetDisconnect, new Object[] { - invoke(mGraphEnv, sGraphEnvironmentGetContext)}); - } - - // The VideoModule will call this to notify that the camera is being - // released to the outside world. This call should happen after the - // stopRecording call. Else, the effects may throw an exception. - // With the recording stopped, the stopPreview call will not try to - // release the camera again. - // This must be called in onPause() if the effects are ON. - public synchronized void disconnectCamera() { - if (mLogVerbose) Log.v(TAG, "Disconnecting the effects from Camera"); - stopCameraPreview(); - mCameraDevice = null; - } - - // In a normal case, when the disconnect is not called, we should not - // set the camera device to null, since on return callback, we try to - // enable 3A locks, which need the cameradevice. - public synchronized void stopCameraPreview() { - if (mLogVerbose) Log.v(TAG, "Stopping camera preview."); - if (mCameraDevice == null) { - Log.d(TAG, "Camera already null. Nothing to disconnect"); - return; - } - mCameraDevice.stopPreview(); - mCameraDevice.setPreviewTexture(null); - } - - // Stop and release effect resources - public synchronized void stopPreview() { - if (mLogVerbose) Log.v(TAG, "Stopping preview (" + this + ")"); - switch (mState) { - case STATE_CONFIGURE: - Log.w(TAG, "StopPreview called when preview not active!"); - return; - case STATE_RELEASED: - throw new RuntimeException("stopPreview called on released EffectsRecorder!"); - default: - break; - } - - if (mState == STATE_RECORD) { - stopRecording(); - } - - mCurrentEffect = EFFECT_NONE; - - // This will not do anything if the camera has already been disconnected. - stopCameraPreview(); - - mState = STATE_CONFIGURE; - mOldRunner = mRunner; - invoke(mRunner, sGraphRunnerStop); - mRunner = null; - // Rest of stop and release handled in mRunnerDoneCallback - } - - // Try to enable/disable video stabilization if supported; otherwise return false - // It is called from a synchronized block. - boolean tryEnableVideoStabilization(boolean toggle) { - if (mLogVerbose) Log.v(TAG, "tryEnableVideoStabilization."); - if (mCameraDevice == null) { - Log.d(TAG, "Camera already null. Not enabling video stabilization."); - return false; - } - Camera.Parameters params = mCameraDevice.getParameters(); - - String vstabSupported = params.get("video-stabilization-supported"); - if ("true".equals(vstabSupported)) { - if (mLogVerbose) Log.v(TAG, "Setting video stabilization to " + toggle); - params.set("video-stabilization", toggle ? "true" : "false"); - mCameraDevice.setParameters(params); - return true; - } - if (mLogVerbose) Log.v(TAG, "Video stabilization not supported"); - return false; - } - - // Try to enable/disable 3A locks if supported; otherwise return false - @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) - synchronized boolean tryEnable3ALocks(boolean toggle) { - if (mLogVerbose) Log.v(TAG, "tryEnable3ALocks"); - if (mCameraDevice == null) { - Log.d(TAG, "Camera already null. Not tryenabling 3A locks."); - return false; - } - Camera.Parameters params = mCameraDevice.getParameters(); - if (CameraUtil.isAutoExposureLockSupported(params) && - CameraUtil.isAutoWhiteBalanceLockSupported(params)) { - params.setAutoExposureLock(toggle); - params.setAutoWhiteBalanceLock(toggle); - mCameraDevice.setParameters(params); - return true; - } - return false; - } - - // Try to enable/disable 3A locks if supported; otherwise, throw error - // Use this when locks are essential to success - synchronized void enable3ALocks(boolean toggle) { - if (mLogVerbose) Log.v(TAG, "Enable3ALocks"); - if (mCameraDevice == null) { - Log.d(TAG, "Camera already null. Not enabling 3A locks."); - return; - } - Camera.Parameters params = mCameraDevice.getParameters(); - if (!tryEnable3ALocks(toggle)) { - throw new RuntimeException("Attempt to lock 3A on camera with no locking support!"); - } - } - - static class SerializableInvocationHandler - implements InvocationHandler, Serializable { - private final int mEffectsRecorderIndex; - public SerializableInvocationHandler(int index) { - mEffectsRecorderIndex = index; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable { - if (sEffectsRecorder == null) return null; - if (mEffectsRecorderIndex != sEffectsRecorderIndex) { - Log.v(TAG, "Ignore old callback " + mEffectsRecorderIndex); - return null; - } - if (method.equals(sObjectEquals)) { - return sEffectsRecorder.invokeObjectEquals(proxy, args); - } else if (method.equals(sObjectToString)) { - return sEffectsRecorder.invokeObjectToString(); - } else if (method.equals(sLearningDoneListenerOnLearningDone)) { - sEffectsRecorder.invokeOnLearningDone(); - } else if (method.equals(sOnRunnerDoneListenerOnRunnerDone)) { - sEffectsRecorder.invokeOnRunnerDone(args); - } else if (method.equals( - sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady)) { - sEffectsRecorder.invokeOnSurfaceTextureSourceReady(args); - } else if (method.equals(sOnRecordingDoneListenerOnRecordingDone)) { - sEffectsRecorder.invokeOnRecordingDone(); - } - return null; - } - } - - // Indicates that all camera/recording activity needs to halt - public synchronized void release() { - if (mLogVerbose) Log.v(TAG, "Releasing (" + this + ")"); - - switch (mState) { - case STATE_RECORD: - case STATE_STARTING_PREVIEW: - case STATE_PREVIEW: - stopPreview(); - // Fall-through - default: - if (mSoundPlayer != null) { - mSoundPlayer.release(); - mSoundPlayer = null; - } - mState = STATE_RELEASED; - break; - } - sEffectsRecorder = null; - } - - private void sendMessage(final int effect, final int msg) { - if (mEffectsListener != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - mEffectsListener.onEffectsUpdate(effect, msg); - } - }); - } - } - - private void raiseError(final Exception exception) { - if (mEffectsListener != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - if (mFd != null) { - mEffectsListener.onEffectsError(exception, null); - } else { - mEffectsListener.onEffectsError(exception, mOutputFile); - } - } - }); - } - } - - // invoke method on receiver with no arguments - private Object invoke(Object receiver, Method method) { - try { - return method.invoke(receiver); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - // invoke method on receiver with arguments - private Object invoke(Object receiver, Method method, Object[] args) { - try { - return method.invoke(receiver, args); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private void setInputValue(Object receiver, String key, Object value) { - try { - sFilterSetInputValue.invoke(receiver, new Object[] {key, value}); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private Object newInstance(Constructor<?> ct, Object[] initArgs) { - try { - return ct.newInstance(initArgs); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private Object newInstance(Constructor<?> ct) { - try { - return ct.newInstance(); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private Object getGraphFilter(Object receiver, String name) { - try { - return sFilterGraphGetFilter.invoke(sGraphRunnerGetGraph - .invoke(receiver), new Object[] {name}); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private Object getContextGLEnvironment(Object receiver) { - try { - return sFilterContextGetGLEnvironment - .invoke(sGraphEnvironmentGetContext.invoke(receiver)); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private void getGraphTearDown(Object receiver, Object filterContext) { - try { - sFilterGraphTearDown.invoke(sGraphRunnerGetGraph.invoke(receiver), - new Object[]{filterContext}); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private Object getConstant(Class<?> cls, String name) { - try { - return cls.getDeclaredField(name).get(null); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } -} |