Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.camera;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.graphics.SurfaceTexture;
     22 import android.hardware.Camera;
     23 import android.media.CamcorderProfile;
     24 import android.media.MediaRecorder;
     25 import android.os.Handler;
     26 import android.os.Looper;
     27 import android.util.Log;
     28 
     29 import com.android.gallery3d.common.ApiHelper;
     30 
     31 import java.io.FileDescriptor;
     32 import java.io.IOException;
     33 import java.io.Serializable;
     34 import java.lang.reflect.Constructor;
     35 import java.lang.reflect.InvocationHandler;
     36 import java.lang.reflect.Method;
     37 import java.lang.reflect.Proxy;
     38 
     39 
     40 /**
     41  * Encapsulates the mobile filter framework components needed to record video
     42  * with effects applied. Modeled after MediaRecorder.
     43  */
     44 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
     45 public class EffectsRecorder {
     46     private static final String TAG = "EffectsRecorder";
     47 
     48     private static Class<?> sClassFilter;
     49     private static Method sFilterIsAvailable;
     50     private static EffectsRecorder sEffectsRecorder;
     51     // The index of the current effects recorder.
     52     private static int sEffectsRecorderIndex;
     53 
     54     private static boolean sReflectionInited = false;
     55 
     56     private static Class<?> sClsLearningDoneListener;
     57     private static Class<?> sClsOnRunnerDoneListener;
     58     private static Class<?> sClsOnRecordingDoneListener;
     59     private static Class<?> sClsSurfaceTextureSourceListener;
     60 
     61     private static Method sFilterSetInputValue;
     62 
     63     private static Constructor<?> sCtPoint;
     64     private static Constructor<?> sCtQuad;
     65 
     66     private static Method sLearningDoneListenerOnLearningDone;
     67 
     68     private static Method sObjectEquals;
     69     private static Method sObjectToString;
     70 
     71     private static Class<?> sClsGraphRunner;
     72     private static Method sGraphRunnerGetGraph;
     73     private static Method sGraphRunnerSetDoneCallback;
     74     private static Method sGraphRunnerRun;
     75     private static Method sGraphRunnerGetError;
     76     private static Method sGraphRunnerStop;
     77 
     78     private static Method sFilterGraphGetFilter;
     79     private static Method sFilterGraphTearDown;
     80 
     81     private static Method sOnRunnerDoneListenerOnRunnerDone;
     82 
     83     private static Class<?> sClsGraphEnvironment;
     84     private static Constructor<?> sCtGraphEnvironment;
     85     private static Method sGraphEnvironmentCreateGLEnvironment;
     86     private static Method sGraphEnvironmentGetRunner;
     87     private static Method sGraphEnvironmentAddReferences;
     88     private static Method sGraphEnvironmentLoadGraph;
     89     private static Method sGraphEnvironmentGetContext;
     90 
     91     private static Method sFilterContextGetGLEnvironment;
     92     private static Method sGLEnvironmentIsActive;
     93     private static Method sGLEnvironmentActivate;
     94     private static Method sGLEnvironmentDeactivate;
     95     private static Method sSurfaceTextureTargetDisconnect;
     96     private static Method sOnRecordingDoneListenerOnRecordingDone;
     97     private static Method sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady;
     98 
     99     private Object mLearningDoneListener;
    100     private Object mRunnerDoneCallback;
    101     private Object mSourceReadyCallback;
    102     // A callback to finalize the media after the recording is done.
    103     private Object mRecordingDoneListener;
    104 
    105     static {
    106         try {
    107             sClassFilter = Class.forName("android.filterfw.core.Filter");
    108             sFilterIsAvailable = sClassFilter.getMethod("isAvailable",
    109                     String.class);
    110         } catch (ClassNotFoundException ex) {
    111             Log.v(TAG, "Can't find the class android.filterfw.core.Filter");
    112         } catch (NoSuchMethodException e) {
    113             Log.v(TAG, "Can't find the method Filter.isAvailable");
    114         }
    115     }
    116 
    117     public static final int  EFFECT_NONE        = 0;
    118     public static final int  EFFECT_GOOFY_FACE  = 1;
    119     public static final int  EFFECT_BACKDROPPER = 2;
    120 
    121     public static final int  EFFECT_GF_SQUEEZE     = 0;
    122     public static final int  EFFECT_GF_BIG_EYES    = 1;
    123     public static final int  EFFECT_GF_BIG_MOUTH   = 2;
    124     public static final int  EFFECT_GF_SMALL_MOUTH = 3;
    125     public static final int  EFFECT_GF_BIG_NOSE    = 4;
    126     public static final int  EFFECT_GF_SMALL_EYES  = 5;
    127     public static final int  NUM_OF_GF_EFFECTS = EFFECT_GF_SMALL_EYES + 1;
    128 
    129     public static final int  EFFECT_MSG_STARTED_LEARNING = 0;
    130     public static final int  EFFECT_MSG_DONE_LEARNING    = 1;
    131     public static final int  EFFECT_MSG_SWITCHING_EFFECT = 2;
    132     public static final int  EFFECT_MSG_EFFECTS_STOPPED  = 3;
    133     public static final int  EFFECT_MSG_RECORDING_DONE   = 4;
    134     public static final int  EFFECT_MSG_PREVIEW_RUNNING  = 5;
    135 
    136     private Context mContext;
    137     private Handler mHandler;
    138 
    139     private CameraManager.CameraProxy mCameraDevice;
    140     private CamcorderProfile mProfile;
    141     private double mCaptureRate = 0;
    142     private SurfaceTexture mPreviewSurfaceTexture;
    143     private int mPreviewWidth;
    144     private int mPreviewHeight;
    145     private MediaRecorder.OnInfoListener mInfoListener;
    146     private MediaRecorder.OnErrorListener mErrorListener;
    147 
    148     private String mOutputFile;
    149     private FileDescriptor mFd;
    150     private int mOrientationHint = 0;
    151     private long mMaxFileSize = 0;
    152     private int mMaxDurationMs = 0;
    153     private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
    154     private int mCameraDisplayOrientation;
    155 
    156     private int mEffect = EFFECT_NONE;
    157     private int mCurrentEffect = EFFECT_NONE;
    158     private EffectsListener mEffectsListener;
    159 
    160     private Object mEffectParameter;
    161 
    162     private Object mGraphEnv;
    163     private int mGraphId;
    164     private Object mRunner = null;
    165     private Object mOldRunner = null;
    166 
    167     private SurfaceTexture mTextureSource;
    168 
    169     private static final int STATE_CONFIGURE              = 0;
    170     private static final int STATE_WAITING_FOR_SURFACE    = 1;
    171     private static final int STATE_STARTING_PREVIEW       = 2;
    172     private static final int STATE_PREVIEW                = 3;
    173     private static final int STATE_RECORD                 = 4;
    174     private static final int STATE_RELEASED               = 5;
    175     private int mState = STATE_CONFIGURE;
    176 
    177     private boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
    178     private SoundClips.Player mSoundPlayer;
    179 
    180     /** Determine if a given effect is supported at runtime
    181      * Some effects require libraries not available on all devices
    182      */
    183     public static boolean isEffectSupported(int effectId) {
    184         if (sFilterIsAvailable == null)  return false;
    185 
    186         try {
    187             switch (effectId) {
    188                 case EFFECT_GOOFY_FACE:
    189                     return (Boolean) sFilterIsAvailable.invoke(null,
    190                             "com.google.android.filterpacks.facedetect.GoofyRenderFilter");
    191                 case EFFECT_BACKDROPPER:
    192                     return (Boolean) sFilterIsAvailable.invoke(null,
    193                             "android.filterpacks.videoproc.BackDropperFilter");
    194                 default:
    195                     return false;
    196             }
    197         } catch (Exception ex) {
    198             Log.e(TAG, "Fail to check filter", ex);
    199         }
    200         return false;
    201     }
    202 
    203     public EffectsRecorder(Context context) {
    204         if (mLogVerbose) Log.v(TAG, "EffectsRecorder created (" + this + ")");
    205 
    206         if (!sReflectionInited) {
    207             try {
    208                 sFilterSetInputValue = sClassFilter.getMethod("setInputValue",
    209                         new Class[] {String.class, Object.class});
    210 
    211                 Class<?> clsPoint = Class.forName("android.filterfw.geometry.Point");
    212                 sCtPoint = clsPoint.getConstructor(new Class[] {float.class,
    213                         float.class});
    214 
    215                 Class<?> clsQuad = Class.forName("android.filterfw.geometry.Quad");
    216                 sCtQuad = clsQuad.getConstructor(new Class[] {clsPoint, clsPoint,
    217                         clsPoint, clsPoint});
    218 
    219                 Class<?> clsBackDropperFilter = Class.forName(
    220                         "android.filterpacks.videoproc.BackDropperFilter");
    221                 sClsLearningDoneListener = Class.forName(
    222                         "android.filterpacks.videoproc.BackDropperFilter$LearningDoneListener");
    223                 sLearningDoneListenerOnLearningDone = sClsLearningDoneListener
    224                         .getMethod("onLearningDone", new Class[] {clsBackDropperFilter});
    225 
    226                 sObjectEquals = Object.class.getMethod("equals", new Class[] {Object.class});
    227                 sObjectToString = Object.class.getMethod("toString");
    228 
    229                 sClsOnRunnerDoneListener = Class.forName(
    230                         "android.filterfw.core.GraphRunner$OnRunnerDoneListener");
    231                 sOnRunnerDoneListenerOnRunnerDone = sClsOnRunnerDoneListener.getMethod(
    232                         "onRunnerDone", new Class[] {int.class});
    233 
    234                 sClsGraphRunner = Class.forName("android.filterfw.core.GraphRunner");
    235                 sGraphRunnerGetGraph = sClsGraphRunner.getMethod("getGraph");
    236                 sGraphRunnerSetDoneCallback = sClsGraphRunner.getMethod(
    237                         "setDoneCallback", new Class[] {sClsOnRunnerDoneListener});
    238                 sGraphRunnerRun = sClsGraphRunner.getMethod("run");
    239                 sGraphRunnerGetError = sClsGraphRunner.getMethod("getError");
    240                 sGraphRunnerStop = sClsGraphRunner.getMethod("stop");
    241 
    242                 Class<?> clsFilterContext = Class.forName("android.filterfw.core.FilterContext");
    243                 sFilterContextGetGLEnvironment = clsFilterContext.getMethod(
    244                         "getGLEnvironment");
    245 
    246                 Class<?> clsFilterGraph = Class.forName("android.filterfw.core.FilterGraph");
    247                 sFilterGraphGetFilter = clsFilterGraph.getMethod("getFilter",
    248                         new Class[] {String.class});
    249                 sFilterGraphTearDown = clsFilterGraph.getMethod("tearDown",
    250                         new Class[] {clsFilterContext});
    251 
    252                 sClsGraphEnvironment = Class.forName("android.filterfw.GraphEnvironment");
    253                 sCtGraphEnvironment = sClsGraphEnvironment.getConstructor();
    254                 sGraphEnvironmentCreateGLEnvironment = sClsGraphEnvironment.getMethod(
    255                         "createGLEnvironment");
    256                 sGraphEnvironmentGetRunner = sClsGraphEnvironment.getMethod(
    257                         "getRunner", new Class[] {int.class, int.class});
    258                 sGraphEnvironmentAddReferences = sClsGraphEnvironment.getMethod(
    259                         "addReferences", new Class[] {Object[].class});
    260                 sGraphEnvironmentLoadGraph = sClsGraphEnvironment.getMethod(
    261                         "loadGraph", new Class[] {Context.class, int.class});
    262                 sGraphEnvironmentGetContext = sClsGraphEnvironment.getMethod(
    263                         "getContext");
    264 
    265                 Class<?> clsGLEnvironment = Class.forName("android.filterfw.core.GLEnvironment");
    266                 sGLEnvironmentIsActive = clsGLEnvironment.getMethod("isActive");
    267                 sGLEnvironmentActivate = clsGLEnvironment.getMethod("activate");
    268                 sGLEnvironmentDeactivate = clsGLEnvironment.getMethod("deactivate");
    269 
    270                 Class<?> clsSurfaceTextureTarget = Class.forName(
    271                         "android.filterpacks.videosrc.SurfaceTextureTarget");
    272                 sSurfaceTextureTargetDisconnect = clsSurfaceTextureTarget.getMethod(
    273                         "disconnect", new Class[] {clsFilterContext});
    274 
    275                 sClsOnRecordingDoneListener = Class.forName(
    276                         "android.filterpacks.videosink.MediaEncoderFilter$OnRecordingDoneListener");
    277                 sOnRecordingDoneListenerOnRecordingDone =
    278                         sClsOnRecordingDoneListener.getMethod("onRecordingDone");
    279 
    280                 sClsSurfaceTextureSourceListener = Class.forName(
    281                         "android.filterpacks.videosrc.SurfaceTextureSource$SurfaceTextureSourceListener");
    282                 sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady =
    283                         sClsSurfaceTextureSourceListener.getMethod(
    284                                 "onSurfaceTextureSourceReady",
    285                                 new Class[] {SurfaceTexture.class});
    286             } catch (Exception ex) {
    287                 throw new RuntimeException(ex);
    288             }
    289 
    290             sReflectionInited = true;
    291         }
    292 
    293         sEffectsRecorderIndex++;
    294         Log.v(TAG, "Current effects recorder index is " + sEffectsRecorderIndex);
    295         sEffectsRecorder = this;
    296         SerializableInvocationHandler sih = new SerializableInvocationHandler(
    297                 sEffectsRecorderIndex);
    298         mLearningDoneListener = Proxy.newProxyInstance(
    299                 sClsLearningDoneListener.getClassLoader(),
    300                 new Class[] {sClsLearningDoneListener}, sih);
    301         mRunnerDoneCallback = Proxy.newProxyInstance(
    302                 sClsOnRunnerDoneListener.getClassLoader(),
    303                 new Class[] {sClsOnRunnerDoneListener}, sih);
    304         mSourceReadyCallback = Proxy.newProxyInstance(
    305                 sClsSurfaceTextureSourceListener.getClassLoader(),
    306                 new Class[] {sClsSurfaceTextureSourceListener}, sih);
    307         mRecordingDoneListener =  Proxy.newProxyInstance(
    308                 sClsOnRecordingDoneListener.getClassLoader(),
    309                 new Class[] {sClsOnRecordingDoneListener}, sih);
    310 
    311         mContext = context;
    312         mHandler = new Handler(Looper.getMainLooper());
    313         mSoundPlayer = SoundClips.getPlayer(context);
    314     }
    315 
    316     public synchronized void setCamera(CameraManager.CameraProxy cameraDevice) {
    317         switch (mState) {
    318             case STATE_PREVIEW:
    319                 throw new RuntimeException("setCamera cannot be called while previewing!");
    320             case STATE_RECORD:
    321                 throw new RuntimeException("setCamera cannot be called while recording!");
    322             case STATE_RELEASED:
    323                 throw new RuntimeException("setCamera called on an already released recorder!");
    324             default:
    325                 break;
    326         }
    327 
    328         mCameraDevice = cameraDevice;
    329     }
    330 
    331     public void setProfile(CamcorderProfile profile) {
    332         switch (mState) {
    333             case STATE_RECORD:
    334                 throw new RuntimeException("setProfile cannot be called while recording!");
    335             case STATE_RELEASED:
    336                 throw new RuntimeException("setProfile called on an already released recorder!");
    337             default:
    338                 break;
    339         }
    340         mProfile = profile;
    341     }
    342 
    343     public void setOutputFile(String outputFile) {
    344         switch (mState) {
    345             case STATE_RECORD:
    346                 throw new RuntimeException("setOutputFile cannot be called while recording!");
    347             case STATE_RELEASED:
    348                 throw new RuntimeException("setOutputFile called on an already released recorder!");
    349             default:
    350                 break;
    351         }
    352 
    353         mOutputFile = outputFile;
    354         mFd = null;
    355     }
    356 
    357     public void setOutputFile(FileDescriptor fd) {
    358         switch (mState) {
    359             case STATE_RECORD:
    360                 throw new RuntimeException("setOutputFile cannot be called while recording!");
    361             case STATE_RELEASED:
    362                 throw new RuntimeException("setOutputFile called on an already released recorder!");
    363             default:
    364                 break;
    365         }
    366 
    367         mOutputFile = null;
    368         mFd = fd;
    369     }
    370 
    371     /**
    372      * Sets the maximum filesize (in bytes) of the recording session.
    373      * This will be passed on to the MediaEncoderFilter and then to the
    374      * MediaRecorder ultimately. If zero or negative, the MediaRecorder will
    375      * disable the limit
    376     */
    377     public synchronized void setMaxFileSize(long maxFileSize) {
    378         switch (mState) {
    379             case STATE_RECORD:
    380                 throw new RuntimeException("setMaxFileSize cannot be called while recording!");
    381             case STATE_RELEASED:
    382                 throw new RuntimeException(
    383                     "setMaxFileSize called on an already released recorder!");
    384             default:
    385                 break;
    386         }
    387         mMaxFileSize = maxFileSize;
    388     }
    389 
    390     /**
    391     * Sets the maximum recording duration (in ms) for the next recording session
    392     * Setting it to zero (the default) disables the limit.
    393     */
    394     public synchronized void setMaxDuration(int maxDurationMs) {
    395         switch (mState) {
    396             case STATE_RECORD:
    397                 throw new RuntimeException("setMaxDuration cannot be called while recording!");
    398             case STATE_RELEASED:
    399                 throw new RuntimeException(
    400                     "setMaxDuration called on an already released recorder!");
    401             default:
    402                 break;
    403         }
    404         mMaxDurationMs = maxDurationMs;
    405     }
    406 
    407 
    408     public void setCaptureRate(double fps) {
    409         switch (mState) {
    410             case STATE_RECORD:
    411                 throw new RuntimeException("setCaptureRate cannot be called while recording!");
    412             case STATE_RELEASED:
    413                 throw new RuntimeException(
    414                     "setCaptureRate called on an already released recorder!");
    415             default:
    416                 break;
    417         }
    418 
    419         if (mLogVerbose) Log.v(TAG, "Setting time lapse capture rate to " + fps + " fps");
    420         mCaptureRate = fps;
    421     }
    422 
    423     public void setPreviewSurfaceTexture(SurfaceTexture previewSurfaceTexture,
    424                                   int previewWidth,
    425                                   int previewHeight) {
    426         if (mLogVerbose) Log.v(TAG, "setPreviewSurfaceTexture(" + this + ")");
    427         switch (mState) {
    428             case STATE_RECORD:
    429                 throw new RuntimeException(
    430                     "setPreviewSurfaceTexture cannot be called while recording!");
    431             case STATE_RELEASED:
    432                 throw new RuntimeException(
    433                     "setPreviewSurfaceTexture called on an already released recorder!");
    434             default:
    435                 break;
    436         }
    437 
    438         mPreviewSurfaceTexture = previewSurfaceTexture;
    439         mPreviewWidth = previewWidth;
    440         mPreviewHeight = previewHeight;
    441 
    442         switch (mState) {
    443             case STATE_WAITING_FOR_SURFACE:
    444                 startPreview();
    445                 break;
    446             case STATE_STARTING_PREVIEW:
    447             case STATE_PREVIEW:
    448                 initializeEffect(true);
    449                 break;
    450         }
    451     }
    452 
    453     public void setEffect(int effect, Object effectParameter) {
    454         if (mLogVerbose) Log.v(TAG,
    455                                "setEffect: effect ID " + effect +
    456                                ", parameter " + effectParameter.toString());
    457         switch (mState) {
    458             case STATE_RECORD:
    459                 throw new RuntimeException("setEffect cannot be called while recording!");
    460             case STATE_RELEASED:
    461                 throw new RuntimeException("setEffect called on an already released recorder!");
    462             default:
    463                 break;
    464         }
    465 
    466         mEffect = effect;
    467         mEffectParameter = effectParameter;
    468 
    469         if (mState == STATE_PREVIEW ||
    470                 mState == STATE_STARTING_PREVIEW) {
    471             initializeEffect(false);
    472         }
    473     }
    474 
    475     public interface EffectsListener {
    476         public void onEffectsUpdate(int effectId, int effectMsg);
    477         public void onEffectsError(Exception exception, String filePath);
    478     }
    479 
    480     public void setEffectsListener(EffectsListener listener) {
    481         mEffectsListener = listener;
    482     }
    483 
    484     private void setFaceDetectOrientation() {
    485         if (mCurrentEffect == EFFECT_GOOFY_FACE) {
    486             Object rotateFilter = getGraphFilter(mRunner, "rotate");
    487             Object metaRotateFilter = getGraphFilter(mRunner, "metarotate");
    488             setInputValue(rotateFilter, "rotation", mOrientationHint);
    489             int reverseDegrees = (360 - mOrientationHint) % 360;
    490             setInputValue(metaRotateFilter, "rotation", reverseDegrees);
    491         }
    492     }
    493 
    494     private void setRecordingOrientation() {
    495         if (mState != STATE_RECORD && mRunner != null) {
    496             Object bl = newInstance(sCtPoint, new Object[] {0, 0});
    497             Object br = newInstance(sCtPoint, new Object[] {1, 0});
    498             Object tl = newInstance(sCtPoint, new Object[] {0, 1});
    499             Object tr = newInstance(sCtPoint, new Object[] {1, 1});
    500             Object recordingRegion;
    501             if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
    502                 // The back camera is not mirrored, so use a identity transform
    503                 recordingRegion = newInstance(sCtQuad, new Object[] {bl, br, tl, tr});
    504             } else {
    505                 // Recording region needs to be tweaked for front cameras, since they
    506                 // mirror their preview
    507                 if (mOrientationHint == 0 || mOrientationHint == 180) {
    508                     // Horizontal flip in landscape
    509                     recordingRegion = newInstance(sCtQuad, new Object[] {br, bl, tr, tl});
    510                 } else {
    511                     // Horizontal flip in portrait
    512                     recordingRegion = newInstance(sCtQuad, new Object[] {tl, tr, bl, br});
    513                 }
    514             }
    515             Object recorder = getGraphFilter(mRunner, "recorder");
    516             setInputValue(recorder, "inputRegion", recordingRegion);
    517         }
    518     }
    519     public void setOrientationHint(int degrees) {
    520         switch (mState) {
    521             case STATE_RELEASED:
    522                 throw new RuntimeException(
    523                         "setOrientationHint called on an already released recorder!");
    524             default:
    525                 break;
    526         }
    527         if (mLogVerbose) Log.v(TAG, "Setting orientation hint to: " + degrees);
    528         mOrientationHint = degrees;
    529         setFaceDetectOrientation();
    530         setRecordingOrientation();
    531     }
    532 
    533     public void setCameraDisplayOrientation(int orientation) {
    534         if (mState != STATE_CONFIGURE) {
    535             throw new RuntimeException(
    536                 "setCameraDisplayOrientation called after configuration!");
    537         }
    538         mCameraDisplayOrientation = orientation;
    539     }
    540 
    541     public void setCameraFacing(int facing) {
    542         switch (mState) {
    543             case STATE_RELEASED:
    544                 throw new RuntimeException(
    545                     "setCameraFacing called on alrady released recorder!");
    546             default:
    547                 break;
    548         }
    549         mCameraFacing = facing;
    550         setRecordingOrientation();
    551     }
    552 
    553     public void setOnInfoListener(MediaRecorder.OnInfoListener infoListener) {
    554         switch (mState) {
    555             case STATE_RECORD:
    556                 throw new RuntimeException("setInfoListener cannot be called while recording!");
    557             case STATE_RELEASED:
    558                 throw new RuntimeException(
    559                     "setInfoListener called on an already released recorder!");
    560             default:
    561                 break;
    562         }
    563         mInfoListener = infoListener;
    564     }
    565 
    566     public void setOnErrorListener(MediaRecorder.OnErrorListener errorListener) {
    567         switch (mState) {
    568             case STATE_RECORD:
    569                 throw new RuntimeException("setErrorListener cannot be called while recording!");
    570             case STATE_RELEASED:
    571                 throw new RuntimeException(
    572                     "setErrorListener called on an already released recorder!");
    573             default:
    574                 break;
    575         }
    576         mErrorListener = errorListener;
    577     }
    578 
    579     private void initializeFilterFramework() {
    580         mGraphEnv = newInstance(sCtGraphEnvironment);
    581         invoke(mGraphEnv, sGraphEnvironmentCreateGLEnvironment);
    582 
    583         int videoFrameWidth = mProfile.videoFrameWidth;
    584         int videoFrameHeight = mProfile.videoFrameHeight;
    585         if (mCameraDisplayOrientation == 90 || mCameraDisplayOrientation == 270) {
    586             int tmp = videoFrameWidth;
    587             videoFrameWidth = videoFrameHeight;
    588             videoFrameHeight = tmp;
    589         }
    590 
    591         invoke(mGraphEnv, sGraphEnvironmentAddReferences,
    592                 new Object[] {new Object[] {
    593                 "textureSourceCallback", mSourceReadyCallback,
    594                 "recordingWidth", videoFrameWidth,
    595                 "recordingHeight", videoFrameHeight,
    596                 "recordingProfile", mProfile,
    597                 "learningDoneListener", mLearningDoneListener,
    598                 "recordingDoneListener", mRecordingDoneListener}});
    599         mRunner = null;
    600         mGraphId = -1;
    601         mCurrentEffect = EFFECT_NONE;
    602     }
    603 
    604     private synchronized void initializeEffect(boolean forceReset) {
    605         if (forceReset ||
    606             mCurrentEffect != mEffect ||
    607             mCurrentEffect == EFFECT_BACKDROPPER) {
    608 
    609             invoke(mGraphEnv, sGraphEnvironmentAddReferences,
    610                     new Object[] {new Object[] {
    611                     "previewSurfaceTexture", mPreviewSurfaceTexture,
    612                     "previewWidth", mPreviewWidth,
    613                     "previewHeight", mPreviewHeight,
    614                     "orientation", mOrientationHint}});
    615             if (mState == STATE_PREVIEW ||
    616                     mState == STATE_STARTING_PREVIEW) {
    617                 // Switching effects while running. Inform video camera.
    618                 sendMessage(mCurrentEffect, EFFECT_MSG_SWITCHING_EFFECT);
    619             }
    620 
    621             switch (mEffect) {
    622                 case EFFECT_GOOFY_FACE:
    623                     mGraphId = (Integer) invoke(mGraphEnv,
    624                             sGraphEnvironmentLoadGraph,
    625                             new Object[] {mContext, R.raw.goofy_face});
    626                     break;
    627                 case EFFECT_BACKDROPPER:
    628                     sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING);
    629                     mGraphId = (Integer) invoke(mGraphEnv,
    630                             sGraphEnvironmentLoadGraph,
    631                             new Object[] {mContext, R.raw.backdropper});
    632                     break;
    633                 default:
    634                     throw new RuntimeException("Unknown effect ID" + mEffect + "!");
    635             }
    636             mCurrentEffect = mEffect;
    637 
    638             mOldRunner = mRunner;
    639             mRunner = invoke(mGraphEnv, sGraphEnvironmentGetRunner,
    640                     new Object[] {mGraphId,
    641                     getConstant(sClsGraphEnvironment, "MODE_ASYNCHRONOUS")});
    642             invoke(mRunner, sGraphRunnerSetDoneCallback, new Object[] {mRunnerDoneCallback});
    643             if (mLogVerbose) {
    644                 Log.v(TAG, "New runner: " + mRunner
    645                       + ". Old runner: " + mOldRunner);
    646             }
    647             if (mState == STATE_PREVIEW ||
    648                     mState == STATE_STARTING_PREVIEW) {
    649                 // Switching effects while running. Stop existing runner.
    650                 // The stop callback will take care of starting new runner.
    651                 mCameraDevice.stopPreview();
    652                 mCameraDevice.setPreviewTextureAsync(null);
    653                 invoke(mOldRunner, sGraphRunnerStop);
    654             }
    655         }
    656 
    657         switch (mCurrentEffect) {
    658             case EFFECT_GOOFY_FACE:
    659                 tryEnableVideoStabilization(true);
    660                 Object goofyFilter = getGraphFilter(mRunner, "goofyrenderer");
    661                 setInputValue(goofyFilter, "currentEffect",
    662                         ((Integer) mEffectParameter).intValue());
    663                 break;
    664             case EFFECT_BACKDROPPER:
    665                 tryEnableVideoStabilization(false);
    666                 Object backgroundSrc = getGraphFilter(mRunner, "background");
    667                 if (ApiHelper.HAS_EFFECTS_RECORDING_CONTEXT_INPUT) {
    668                     // Set the context first before setting sourceUrl to
    669                     // guarantee the content URI get resolved properly.
    670                     setInputValue(backgroundSrc, "context", mContext);
    671                 }
    672                 setInputValue(backgroundSrc, "sourceUrl", mEffectParameter);
    673                 // For front camera, the background video needs to be mirrored in the
    674                 // backdropper filter
    675                 if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    676                     Object replacer = getGraphFilter(mRunner, "replacer");
    677                     setInputValue(replacer, "mirrorBg", true);
    678                     if (mLogVerbose) Log.v(TAG, "Setting the background to be mirrored");
    679                 }
    680                 break;
    681             default:
    682                 break;
    683         }
    684         setFaceDetectOrientation();
    685         setRecordingOrientation();
    686     }
    687 
    688     public synchronized void startPreview() {
    689         if (mLogVerbose) Log.v(TAG, "Starting preview (" + this + ")");
    690 
    691         switch (mState) {
    692             case STATE_STARTING_PREVIEW:
    693             case STATE_PREVIEW:
    694                 // Already running preview
    695                 Log.w(TAG, "startPreview called when already running preview");
    696                 return;
    697             case STATE_RECORD:
    698                 throw new RuntimeException("Cannot start preview when already recording!");
    699             case STATE_RELEASED:
    700                 throw new RuntimeException("setEffect called on an already released recorder!");
    701             default:
    702                 break;
    703         }
    704 
    705         if (mEffect == EFFECT_NONE) {
    706             throw new RuntimeException("No effect selected!");
    707         }
    708         if (mEffectParameter == null) {
    709             throw new RuntimeException("No effect parameter provided!");
    710         }
    711         if (mProfile == null) {
    712             throw new RuntimeException("No recording profile provided!");
    713         }
    714         if (mPreviewSurfaceTexture == null) {
    715             if (mLogVerbose) Log.v(TAG, "Passed a null surface; waiting for valid one");
    716             mState = STATE_WAITING_FOR_SURFACE;
    717             return;
    718         }
    719         if (mCameraDevice == null) {
    720             throw new RuntimeException("No camera to record from!");
    721         }
    722 
    723         if (mLogVerbose) Log.v(TAG, "Initializing filter framework and running the graph.");
    724         initializeFilterFramework();
    725 
    726         initializeEffect(true);
    727 
    728         mState = STATE_STARTING_PREVIEW;
    729         invoke(mRunner, sGraphRunnerRun);
    730         // Rest of preview startup handled in mSourceReadyCallback
    731     }
    732 
    733     private Object invokeObjectEquals(Object proxy, Object[] args) {
    734         return Boolean.valueOf(proxy == args[0]);
    735     }
    736 
    737     private Object invokeObjectToString() {
    738         return "Proxy-" + toString();
    739     }
    740 
    741     private void invokeOnLearningDone() {
    742         if (mLogVerbose) Log.v(TAG, "Learning done callback triggered");
    743         // Called in a processing thread, so have to post message back to UI
    744         // thread
    745         sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_DONE_LEARNING);
    746         enable3ALocks(true);
    747     }
    748 
    749     private void invokeOnRunnerDone(Object[] args) {
    750         int runnerDoneResult = (Integer) args[0];
    751         synchronized (EffectsRecorder.this) {
    752             if (mLogVerbose) {
    753                 Log.v(TAG,
    754                       "Graph runner done (" + EffectsRecorder.this
    755                       + ", mRunner " + mRunner
    756                       + ", mOldRunner " + mOldRunner + ")");
    757             }
    758             if (runnerDoneResult ==
    759                     (Integer) getConstant(sClsGraphRunner, "RESULT_ERROR")) {
    760                 // Handle error case
    761                 Log.e(TAG, "Error running filter graph!");
    762                 Exception e = null;
    763                 if (mRunner != null) {
    764                     e = (Exception) invoke(mRunner, sGraphRunnerGetError);
    765                 } else if (mOldRunner != null) {
    766                     e = (Exception) invoke(mOldRunner, sGraphRunnerGetError);
    767                 }
    768                 raiseError(e);
    769             }
    770             if (mOldRunner != null) {
    771                 // Tear down old graph if available
    772                 if (mLogVerbose) Log.v(TAG, "Tearing down old graph.");
    773                 Object glEnv = getContextGLEnvironment(mGraphEnv);
    774                 if (glEnv != null && !(Boolean) invoke(glEnv, sGLEnvironmentIsActive)) {
    775                     invoke(glEnv, sGLEnvironmentActivate);
    776                 }
    777                 getGraphTearDown(mOldRunner,
    778                         invoke(mGraphEnv, sGraphEnvironmentGetContext));
    779                 if (glEnv != null && (Boolean) invoke(glEnv, sGLEnvironmentIsActive)) {
    780                     invoke(glEnv, sGLEnvironmentDeactivate);
    781                 }
    782                 mOldRunner = null;
    783             }
    784             if (mState == STATE_PREVIEW ||
    785                     mState == STATE_STARTING_PREVIEW) {
    786                 // Switching effects, start up the new runner
    787                 if (mLogVerbose) {
    788                     Log.v(TAG, "Previous effect halted. Running graph again. state: "
    789                             + mState);
    790                 }
    791                 tryEnable3ALocks(false);
    792                 // In case of an error, the graph restarts from beginning and in case
    793                 // of the BACKDROPPER effect, the learner re-learns the background.
    794                 // Hence, we need to show the learning dialogue to the user
    795                 // to avoid recording before the learning is done. Else, the user
    796                 // could start recording before the learning is done and the new
    797                 // background comes up later leading to an end result video
    798                 // with a heterogeneous background.
    799                 // For BACKDROPPER effect, this path is also executed sometimes at
    800                 // the end of a normal recording session. In such a case, the graph
    801                 // does not restart and hence the learner does not re-learn. So we
    802                 // do not want to show the learning dialogue then.
    803                 if (runnerDoneResult == (Integer) getConstant(
    804                         sClsGraphRunner, "RESULT_ERROR")
    805                         && mCurrentEffect == EFFECT_BACKDROPPER) {
    806                     sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING);
    807                 }
    808                 invoke(mRunner, sGraphRunnerRun);
    809             } else if (mState != STATE_RELEASED) {
    810                 // Shutting down effects
    811                 if (mLogVerbose) Log.v(TAG, "Runner halted, restoring direct preview");
    812                 tryEnable3ALocks(false);
    813                 sendMessage(EFFECT_NONE, EFFECT_MSG_EFFECTS_STOPPED);
    814             } else {
    815                 // STATE_RELEASED - camera will be/has been released as well, do nothing.
    816             }
    817         }
    818     }
    819 
    820     private void invokeOnSurfaceTextureSourceReady(Object[] args) {
    821         SurfaceTexture source = (SurfaceTexture) args[0];
    822         if (mLogVerbose) Log.v(TAG, "SurfaceTexture ready callback received");
    823         synchronized (EffectsRecorder.this) {
    824             mTextureSource = source;
    825 
    826             if (mState == STATE_CONFIGURE) {
    827                 // Stop preview happened while the runner was doing startup tasks
    828                 // Since we haven't started anything up, don't do anything
    829                 // Rest of cleanup will happen in onRunnerDone
    830                 if (mLogVerbose) Log.v(TAG, "Ready callback: Already stopped, skipping.");
    831                 return;
    832             }
    833             if (mState == STATE_RELEASED) {
    834                 // EffectsRecorder has been released, so don't touch the camera device
    835                 // or anything else
    836                 if (mLogVerbose) Log.v(TAG, "Ready callback: Already released, skipping.");
    837                 return;
    838             }
    839             if (source == null) {
    840                 if (mLogVerbose) {
    841                     Log.v(TAG, "Ready callback: source null! Looks like graph was closed!");
    842                 }
    843                 if (mState == STATE_PREVIEW ||
    844                         mState == STATE_STARTING_PREVIEW ||
    845                         mState == STATE_RECORD) {
    846                     // A null source here means the graph is shutting down
    847                     // unexpectedly, so we need to turn off preview before
    848                     // the surface texture goes away.
    849                     if (mLogVerbose) {
    850                         Log.v(TAG, "Ready callback: State: " + mState
    851                                 + ". stopCameraPreview");
    852                     }
    853 
    854                     stopCameraPreview();
    855                 }
    856                 return;
    857             }
    858 
    859             // Lock AE/AWB to reduce transition flicker
    860             tryEnable3ALocks(true);
    861 
    862             mCameraDevice.stopPreview();
    863             if (mLogVerbose) Log.v(TAG, "Runner active, connecting effects preview");
    864             mCameraDevice.setPreviewTextureAsync(mTextureSource);
    865 
    866             mCameraDevice.startPreviewAsync();
    867 
    868             // Unlock AE/AWB after preview started
    869             tryEnable3ALocks(false);
    870 
    871             mState = STATE_PREVIEW;
    872 
    873             if (mLogVerbose) Log.v(TAG, "Start preview/effect switch complete");
    874 
    875             // Sending a message to listener that preview is complete
    876             sendMessage(mCurrentEffect, EFFECT_MSG_PREVIEW_RUNNING);
    877         }
    878     }
    879 
    880     private void invokeOnRecordingDone() {
    881         // Forward the callback to the VideoModule object (as an asynchronous event).
    882         if (mLogVerbose) Log.v(TAG, "Recording done callback triggered");
    883         sendMessage(EFFECT_NONE, EFFECT_MSG_RECORDING_DONE);
    884     }
    885 
    886     public synchronized void startRecording() {
    887         if (mLogVerbose) Log.v(TAG, "Starting recording (" + this + ")");
    888 
    889         switch (mState) {
    890             case STATE_RECORD:
    891                 throw new RuntimeException("Already recording, cannot begin anew!");
    892             case STATE_RELEASED:
    893                 throw new RuntimeException(
    894                     "startRecording called on an already released recorder!");
    895             default:
    896                 break;
    897         }
    898 
    899         if ((mOutputFile == null) && (mFd == null)) {
    900             throw new RuntimeException("No output file name or descriptor provided!");
    901         }
    902 
    903         if (mState == STATE_CONFIGURE) {
    904             startPreview();
    905         }
    906 
    907         Object recorder = getGraphFilter(mRunner, "recorder");
    908         if (mFd != null) {
    909             setInputValue(recorder, "outputFileDescriptor", mFd);
    910         } else {
    911             setInputValue(recorder, "outputFile", mOutputFile);
    912         }
    913         // It is ok to set the audiosource without checking for timelapse here
    914         // since that check will be done in the MediaEncoderFilter itself
    915         setInputValue(recorder, "audioSource", MediaRecorder.AudioSource.CAMCORDER);
    916         setInputValue(recorder, "recordingProfile", mProfile);
    917         setInputValue(recorder, "orientationHint", mOrientationHint);
    918         // Important to set the timelapseinterval to 0 if the capture rate is not >0
    919         // since the recorder does not get created every time the recording starts.
    920         // The recorder infers whether the capture is timelapsed based on the value of
    921         // this interval
    922         boolean captureTimeLapse = mCaptureRate > 0;
    923         if (captureTimeLapse) {
    924             double timeBetweenFrameCapture = 1 / mCaptureRate;
    925             setInputValue(recorder, "timelapseRecordingIntervalUs",
    926                     (long) (1000000 * timeBetweenFrameCapture));
    927 
    928         } else {
    929             setInputValue(recorder, "timelapseRecordingIntervalUs", 0L);
    930         }
    931 
    932         if (mInfoListener != null) {
    933             setInputValue(recorder, "infoListener", mInfoListener);
    934         }
    935         if (mErrorListener != null) {
    936             setInputValue(recorder, "errorListener", mErrorListener);
    937         }
    938         setInputValue(recorder, "maxFileSize", mMaxFileSize);
    939         setInputValue(recorder, "maxDurationMs", mMaxDurationMs);
    940         setInputValue(recorder, "recording", true);
    941         mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING);
    942         mState = STATE_RECORD;
    943     }
    944 
    945     public synchronized void stopRecording() {
    946         if (mLogVerbose) Log.v(TAG, "Stop recording (" + this + ")");
    947 
    948         switch (mState) {
    949             case STATE_CONFIGURE:
    950             case STATE_STARTING_PREVIEW:
    951             case STATE_PREVIEW:
    952                 Log.w(TAG, "StopRecording called when recording not active!");
    953                 return;
    954             case STATE_RELEASED:
    955                 throw new RuntimeException("stopRecording called on released EffectsRecorder!");
    956             default:
    957                 break;
    958         }
    959         Object recorder = getGraphFilter(mRunner, "recorder");
    960         setInputValue(recorder, "recording", false);
    961         mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING);
    962         mState = STATE_PREVIEW;
    963     }
    964 
    965     // Called to tell the filter graph that the display surfacetexture is not valid anymore.
    966     // So the filter graph should not hold any reference to the surface created with that.
    967     public synchronized void disconnectDisplay() {
    968         if (mLogVerbose) Log.v(TAG, "Disconnecting the graph from the " +
    969             "SurfaceTexture");
    970         Object display = getGraphFilter(mRunner, "display");
    971         invoke(display, sSurfaceTextureTargetDisconnect, new Object[] {
    972                 invoke(mGraphEnv, sGraphEnvironmentGetContext)});
    973     }
    974 
    975     // The VideoModule will call this to notify that the camera is being
    976     // released to the outside world. This call should happen after the
    977     // stopRecording call. Else, the effects may throw an exception.
    978     // With the recording stopped, the stopPreview call will not try to
    979     // release the camera again.
    980     // This must be called in onPause() if the effects are ON.
    981     public synchronized void disconnectCamera() {
    982         if (mLogVerbose) Log.v(TAG, "Disconnecting the effects from Camera");
    983         stopCameraPreview();
    984         mCameraDevice = null;
    985     }
    986 
    987     // In a normal case, when the disconnect is not called, we should not
    988     // set the camera device to null, since on return callback, we try to
    989     // enable 3A locks, which need the cameradevice.
    990     public synchronized void stopCameraPreview() {
    991         if (mLogVerbose) Log.v(TAG, "Stopping camera preview.");
    992         if (mCameraDevice == null) {
    993             Log.d(TAG, "Camera already null. Nothing to disconnect");
    994             return;
    995         }
    996         mCameraDevice.stopPreview();
    997         mCameraDevice.setPreviewTextureAsync(null);
    998     }
    999 
   1000     // Stop and release effect resources
   1001     public synchronized void stopPreview() {
   1002         if (mLogVerbose) Log.v(TAG, "Stopping preview (" + this + ")");
   1003         switch (mState) {
   1004             case STATE_CONFIGURE:
   1005                 Log.w(TAG, "StopPreview called when preview not active!");
   1006                 return;
   1007             case STATE_RELEASED:
   1008                 throw new RuntimeException("stopPreview called on released EffectsRecorder!");
   1009             default:
   1010                 break;
   1011         }
   1012 
   1013         if (mState == STATE_RECORD) {
   1014             stopRecording();
   1015         }
   1016 
   1017         mCurrentEffect = EFFECT_NONE;
   1018 
   1019         // This will not do anything if the camera has already been disconnected.
   1020         stopCameraPreview();
   1021 
   1022         mState = STATE_CONFIGURE;
   1023         mOldRunner = mRunner;
   1024         invoke(mRunner, sGraphRunnerStop);
   1025         mRunner = null;
   1026         // Rest of stop and release handled in mRunnerDoneCallback
   1027     }
   1028 
   1029     // Try to enable/disable video stabilization if supported; otherwise return false
   1030     // It is called from a synchronized block.
   1031     boolean tryEnableVideoStabilization(boolean toggle) {
   1032         if (mLogVerbose) Log.v(TAG, "tryEnableVideoStabilization.");
   1033         if (mCameraDevice == null) {
   1034             Log.d(TAG, "Camera already null. Not enabling video stabilization.");
   1035             return false;
   1036         }
   1037         Camera.Parameters params = mCameraDevice.getParameters();
   1038 
   1039         String vstabSupported = params.get("video-stabilization-supported");
   1040         if ("true".equals(vstabSupported)) {
   1041             if (mLogVerbose) Log.v(TAG, "Setting video stabilization to " + toggle);
   1042             params.set("video-stabilization", toggle ? "true" : "false");
   1043             mCameraDevice.setParameters(params);
   1044             return true;
   1045         }
   1046         if (mLogVerbose) Log.v(TAG, "Video stabilization not supported");
   1047         return false;
   1048     }
   1049 
   1050     // Try to enable/disable 3A locks if supported; otherwise return false
   1051     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
   1052     synchronized boolean tryEnable3ALocks(boolean toggle) {
   1053         if (mLogVerbose) Log.v(TAG, "tryEnable3ALocks");
   1054         if (mCameraDevice == null) {
   1055             Log.d(TAG, "Camera already null. Not tryenabling 3A locks.");
   1056             return false;
   1057         }
   1058         Camera.Parameters params = mCameraDevice.getParameters();
   1059         if (Util.isAutoExposureLockSupported(params) &&
   1060             Util.isAutoWhiteBalanceLockSupported(params)) {
   1061             params.setAutoExposureLock(toggle);
   1062             params.setAutoWhiteBalanceLock(toggle);
   1063             mCameraDevice.setParameters(params);
   1064             return true;
   1065         }
   1066         return false;
   1067     }
   1068 
   1069     // Try to enable/disable 3A locks if supported; otherwise, throw error
   1070     // Use this when locks are essential to success
   1071     synchronized void enable3ALocks(boolean toggle) {
   1072         if (mLogVerbose) Log.v(TAG, "Enable3ALocks");
   1073         if (mCameraDevice == null) {
   1074             Log.d(TAG, "Camera already null. Not enabling 3A locks.");
   1075             return;
   1076         }
   1077         Camera.Parameters params = mCameraDevice.getParameters();
   1078         if (!tryEnable3ALocks(toggle)) {
   1079             throw new RuntimeException("Attempt to lock 3A on camera with no locking support!");
   1080         }
   1081     }
   1082 
   1083     static class SerializableInvocationHandler
   1084             implements InvocationHandler, Serializable {
   1085         private final int mEffectsRecorderIndex;
   1086         public SerializableInvocationHandler(int index) {
   1087             mEffectsRecorderIndex = index;
   1088         }
   1089 
   1090         @Override
   1091         public Object invoke(Object proxy, Method method, Object[] args)
   1092                 throws Throwable {
   1093             if (sEffectsRecorder == null) return null;
   1094             if (mEffectsRecorderIndex != sEffectsRecorderIndex) {
   1095                 Log.v(TAG, "Ignore old callback " + mEffectsRecorderIndex);
   1096                 return null;
   1097             }
   1098             if (method.equals(sObjectEquals)) {
   1099                 return sEffectsRecorder.invokeObjectEquals(proxy, args);
   1100             } else if (method.equals(sObjectToString)) {
   1101                 return sEffectsRecorder.invokeObjectToString();
   1102             } else if (method.equals(sLearningDoneListenerOnLearningDone)) {
   1103                 sEffectsRecorder.invokeOnLearningDone();
   1104             } else if (method.equals(sOnRunnerDoneListenerOnRunnerDone)) {
   1105                 sEffectsRecorder.invokeOnRunnerDone(args);
   1106             } else if (method.equals(
   1107                     sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady)) {
   1108                 sEffectsRecorder.invokeOnSurfaceTextureSourceReady(args);
   1109             } else if (method.equals(sOnRecordingDoneListenerOnRecordingDone)) {
   1110                 sEffectsRecorder.invokeOnRecordingDone();
   1111             }
   1112             return null;
   1113         }
   1114     }
   1115 
   1116     // Indicates that all camera/recording activity needs to halt
   1117     public synchronized void release() {
   1118         if (mLogVerbose) Log.v(TAG, "Releasing (" + this + ")");
   1119 
   1120         switch (mState) {
   1121             case STATE_RECORD:
   1122             case STATE_STARTING_PREVIEW:
   1123             case STATE_PREVIEW:
   1124                 stopPreview();
   1125                 // Fall-through
   1126             default:
   1127                 if (mSoundPlayer != null) {
   1128                     mSoundPlayer.release();
   1129                     mSoundPlayer = null;
   1130                 }
   1131                 mState = STATE_RELEASED;
   1132                 break;
   1133         }
   1134         sEffectsRecorder = null;
   1135     }
   1136 
   1137     private void sendMessage(final int effect, final int msg) {
   1138         if (mEffectsListener != null) {
   1139             mHandler.post(new Runnable() {
   1140                 @Override
   1141                 public void run() {
   1142                     mEffectsListener.onEffectsUpdate(effect, msg);
   1143                 }
   1144             });
   1145         }
   1146     }
   1147 
   1148     private void raiseError(final Exception exception) {
   1149         if (mEffectsListener != null) {
   1150             mHandler.post(new Runnable() {
   1151                 @Override
   1152                 public void run() {
   1153                     if (mFd != null) {
   1154                         mEffectsListener.onEffectsError(exception, null);
   1155                     } else {
   1156                         mEffectsListener.onEffectsError(exception, mOutputFile);
   1157                     }
   1158                 }
   1159             });
   1160         }
   1161     }
   1162 
   1163     // invoke method on receiver with no arguments
   1164     private Object invoke(Object receiver, Method method) {
   1165         try {
   1166             return method.invoke(receiver);
   1167         } catch (Exception ex) {
   1168             throw new RuntimeException(ex);
   1169         }
   1170     }
   1171 
   1172     // invoke method on receiver with arguments
   1173     private Object invoke(Object receiver, Method method, Object[] args) {
   1174         try {
   1175             return method.invoke(receiver, args);
   1176         } catch (Exception ex) {
   1177             throw new RuntimeException(ex);
   1178         }
   1179     }
   1180 
   1181     private void setInputValue(Object receiver, String key, Object value) {
   1182         try {
   1183             sFilterSetInputValue.invoke(receiver, new Object[] {key, value});
   1184         } catch (Exception ex) {
   1185             throw new RuntimeException(ex);
   1186         }
   1187     }
   1188 
   1189     private Object newInstance(Constructor<?> ct, Object[] initArgs) {
   1190         try {
   1191             return ct.newInstance(initArgs);
   1192         } catch (Exception ex) {
   1193             throw new RuntimeException(ex);
   1194         }
   1195     }
   1196 
   1197     private Object newInstance(Constructor<?> ct) {
   1198         try {
   1199             return ct.newInstance();
   1200         } catch (Exception ex) {
   1201             throw new RuntimeException(ex);
   1202         }
   1203     }
   1204 
   1205     private Object getGraphFilter(Object receiver, String name) {
   1206         try {
   1207             return sFilterGraphGetFilter.invoke(sGraphRunnerGetGraph
   1208                     .invoke(receiver), new Object[] {name});
   1209         } catch (Exception ex) {
   1210             throw new RuntimeException(ex);
   1211         }
   1212     }
   1213 
   1214     private Object getContextGLEnvironment(Object receiver) {
   1215         try {
   1216             return sFilterContextGetGLEnvironment
   1217                     .invoke(sGraphEnvironmentGetContext.invoke(receiver));
   1218         } catch (Exception ex) {
   1219             throw new RuntimeException(ex);
   1220         }
   1221     }
   1222 
   1223     private void getGraphTearDown(Object receiver, Object filterContext) {
   1224         try {
   1225             sFilterGraphTearDown.invoke(sGraphRunnerGetGraph.invoke(receiver),
   1226                     new Object[]{filterContext});
   1227         } catch (Exception ex) {
   1228             throw new RuntimeException(ex);
   1229         }
   1230     }
   1231 
   1232     private Object getConstant(Class<?> cls, String name) {
   1233         try {
   1234             return cls.getDeclaredField(name).get(null);
   1235         } catch (Exception ex) {
   1236             throw new RuntimeException(ex);
   1237         }
   1238     }
   1239 }
   1240