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.content.Context;
     20 import android.filterfw.GraphEnvironment;
     21 import android.filterfw.core.Filter;
     22 import android.filterfw.core.GLEnvironment;
     23 import android.filterfw.core.GraphRunner;
     24 import android.filterfw.core.GraphRunner.OnRunnerDoneListener;
     25 import android.filterfw.geometry.Point;
     26 import android.filterfw.geometry.Quad;
     27 import android.filterpacks.videoproc.BackDropperFilter;
     28 import android.filterpacks.videoproc.BackDropperFilter.LearningDoneListener;
     29 import android.filterpacks.videosink.MediaEncoderFilter.OnRecordingDoneListener;
     30 import android.filterpacks.videosrc.SurfaceTextureSource.SurfaceTextureSourceListener;
     31 import android.filterpacks.videosrc.SurfaceTextureTarget;
     32 import android.graphics.SurfaceTexture;
     33 import android.hardware.Camera;
     34 import android.media.CamcorderProfile;
     35 import android.media.MediaActionSound;
     36 import android.media.MediaRecorder;
     37 import android.os.Handler;
     38 import android.os.Looper;
     39 import android.util.Log;
     40 import android.view.Surface;
     41 
     42 import java.io.FileDescriptor;
     43 import java.io.IOException;
     44 
     45 
     46 /**
     47  * Encapsulates the mobile filter framework components needed to record video with
     48  * effects applied. Modeled after MediaRecorder.
     49  */
     50 public class EffectsRecorder {
     51 
     52     public static final int  EFFECT_NONE        = 0;
     53     public static final int  EFFECT_GOOFY_FACE  = 1;
     54     public static final int  EFFECT_BACKDROPPER = 2;
     55 
     56     public static final int  EFFECT_GF_SQUEEZE     = 0;
     57     public static final int  EFFECT_GF_BIG_EYES    = 1;
     58     public static final int  EFFECT_GF_BIG_MOUTH   = 2;
     59     public static final int  EFFECT_GF_SMALL_MOUTH = 3;
     60     public static final int  EFFECT_GF_BIG_NOSE    = 4;
     61     public static final int  EFFECT_GF_SMALL_EYES  = 5;
     62     public static final int  NUM_OF_GF_EFFECTS = EFFECT_GF_SMALL_EYES + 1;
     63 
     64     public static final int  EFFECT_MSG_STARTED_LEARNING = 0;
     65     public static final int  EFFECT_MSG_DONE_LEARNING    = 1;
     66     public static final int  EFFECT_MSG_SWITCHING_EFFECT = 2;
     67     public static final int  EFFECT_MSG_EFFECTS_STOPPED  = 3;
     68     public static final int  EFFECT_MSG_RECORDING_DONE   = 4;
     69     public static final int  EFFECT_MSG_PREVIEW_RUNNING  = 5;
     70 
     71     private Context mContext;
     72     private Handler mHandler;
     73 
     74     private Camera mCameraDevice;
     75     private CamcorderProfile mProfile;
     76     private double mCaptureRate = 0;
     77     private SurfaceTexture mPreviewSurfaceTexture;
     78     private int mPreviewWidth;
     79     private int mPreviewHeight;
     80     private MediaRecorder.OnInfoListener mInfoListener;
     81     private MediaRecorder.OnErrorListener mErrorListener;
     82 
     83     private String mOutputFile;
     84     private FileDescriptor mFd;
     85     private int mOrientationHint = 0;
     86     private long mMaxFileSize = 0;
     87     private int mMaxDurationMs = 0;
     88     private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
     89     private int mCameraDisplayOrientation;
     90 
     91     private int mEffect = EFFECT_NONE;
     92     private int mCurrentEffect = EFFECT_NONE;
     93     private EffectsListener mEffectsListener;
     94 
     95     private Object mEffectParameter;
     96 
     97     private GraphEnvironment mGraphEnv;
     98     private int mGraphId;
     99     private GraphRunner mRunner = null;
    100     private GraphRunner mOldRunner = null;
    101 
    102     private SurfaceTexture mTextureSource;
    103 
    104     private static final int STATE_CONFIGURE              = 0;
    105     private static final int STATE_WAITING_FOR_SURFACE    = 1;
    106     private static final int STATE_STARTING_PREVIEW       = 2;
    107     private static final int STATE_PREVIEW                = 3;
    108     private static final int STATE_RECORD                 = 4;
    109     private static final int STATE_RELEASED               = 5;
    110     private int mState = STATE_CONFIGURE;
    111 
    112     private boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
    113     private static final String TAG = "EffectsRecorder";
    114     private MediaActionSound mCameraSound;
    115 
    116     /** Determine if a given effect is supported at runtime
    117      * Some effects require libraries not available on all devices
    118      */
    119     public static boolean isEffectSupported(int effectId) {
    120         switch (effectId) {
    121             case EFFECT_GOOFY_FACE:
    122                 return Filter.isAvailable(
    123                     "com.google.android.filterpacks.facedetect.GoofyRenderFilter");
    124             case EFFECT_BACKDROPPER:
    125                 return Filter.isAvailable("android.filterpacks.videoproc.BackDropperFilter");
    126             default:
    127                 return false;
    128         }
    129     }
    130 
    131     public EffectsRecorder(Context context) {
    132         if (mLogVerbose) Log.v(TAG, "EffectsRecorder created (" + this + ")");
    133         mContext = context;
    134         mHandler = new Handler(Looper.getMainLooper());
    135         mCameraSound = new MediaActionSound();
    136         mCameraSound.load(MediaActionSound.START_VIDEO_RECORDING);
    137         mCameraSound.load(MediaActionSound.STOP_VIDEO_RECORDING);
    138     }
    139 
    140     public synchronized void setCamera(Camera cameraDevice) {
    141         switch (mState) {
    142             case STATE_PREVIEW:
    143                 throw new RuntimeException("setCamera cannot be called while previewing!");
    144             case STATE_RECORD:
    145                 throw new RuntimeException("setCamera cannot be called while recording!");
    146             case STATE_RELEASED:
    147                 throw new RuntimeException("setCamera called on an already released recorder!");
    148             default:
    149                 break;
    150         }
    151 
    152         mCameraDevice = cameraDevice;
    153     }
    154 
    155     public void setProfile(CamcorderProfile profile) {
    156         switch (mState) {
    157             case STATE_RECORD:
    158                 throw new RuntimeException("setProfile cannot be called while recording!");
    159             case STATE_RELEASED:
    160                 throw new RuntimeException("setProfile called on an already released recorder!");
    161             default:
    162                 break;
    163         }
    164         mProfile = profile;
    165     }
    166 
    167     public void setOutputFile(String outputFile) {
    168         switch (mState) {
    169             case STATE_RECORD:
    170                 throw new RuntimeException("setOutputFile cannot be called while recording!");
    171             case STATE_RELEASED:
    172                 throw new RuntimeException("setOutputFile called on an already released recorder!");
    173             default:
    174                 break;
    175         }
    176 
    177         mOutputFile = outputFile;
    178         mFd = null;
    179     }
    180 
    181     public void setOutputFile(FileDescriptor fd) {
    182         switch (mState) {
    183             case STATE_RECORD:
    184                 throw new RuntimeException("setOutputFile cannot be called while recording!");
    185             case STATE_RELEASED:
    186                 throw new RuntimeException("setOutputFile called on an already released recorder!");
    187             default:
    188                 break;
    189         }
    190 
    191         mOutputFile = null;
    192         mFd = fd;
    193     }
    194 
    195     /**
    196      * Sets the maximum filesize (in bytes) of the recording session.
    197      * This will be passed on to the MediaEncoderFilter and then to the
    198      * MediaRecorder ultimately. If zero or negative, the MediaRecorder will
    199      * disable the limit
    200     */
    201     public synchronized void setMaxFileSize(long maxFileSize) {
    202         switch (mState) {
    203             case STATE_RECORD:
    204                 throw new RuntimeException("setMaxFileSize cannot be called while recording!");
    205             case STATE_RELEASED:
    206                 throw new RuntimeException(
    207                     "setMaxFileSize called on an already released recorder!");
    208             default:
    209                 break;
    210         }
    211         mMaxFileSize = maxFileSize;
    212     }
    213 
    214     /**
    215     * Sets the maximum recording duration (in ms) for the next recording session
    216     * Setting it to zero (the default) disables the limit.
    217     */
    218     public synchronized void setMaxDuration(int maxDurationMs) {
    219         switch (mState) {
    220             case STATE_RECORD:
    221                 throw new RuntimeException("setMaxDuration cannot be called while recording!");
    222             case STATE_RELEASED:
    223                 throw new RuntimeException(
    224                     "setMaxDuration called on an already released recorder!");
    225             default:
    226                 break;
    227         }
    228         mMaxDurationMs = maxDurationMs;
    229     }
    230 
    231 
    232     public void setCaptureRate(double fps) {
    233         switch (mState) {
    234             case STATE_RECORD:
    235                 throw new RuntimeException("setCaptureRate cannot be called while recording!");
    236             case STATE_RELEASED:
    237                 throw new RuntimeException(
    238                     "setCaptureRate called on an already released recorder!");
    239             default:
    240                 break;
    241         }
    242 
    243         if (mLogVerbose) Log.v(TAG, "Setting time lapse capture rate to " + fps + " fps");
    244         mCaptureRate = fps;
    245     }
    246 
    247     public void setPreviewSurfaceTexture(SurfaceTexture previewSurfaceTexture,
    248                                   int previewWidth,
    249                                   int previewHeight) {
    250         if (mLogVerbose) Log.v(TAG, "setPreviewSurfaceTexture(" + this + ")");
    251         switch (mState) {
    252             case STATE_RECORD:
    253                 throw new RuntimeException(
    254                     "setPreviewSurfaceTexture cannot be called while recording!");
    255             case STATE_RELEASED:
    256                 throw new RuntimeException(
    257                     "setPreviewSurfaceTexture called on an already released recorder!");
    258             default:
    259                 break;
    260         }
    261 
    262         mPreviewSurfaceTexture= previewSurfaceTexture;
    263         mPreviewWidth = previewWidth;
    264         mPreviewHeight = previewHeight;
    265 
    266         switch (mState) {
    267             case STATE_WAITING_FOR_SURFACE:
    268                 startPreview();
    269                 break;
    270             case STATE_STARTING_PREVIEW:
    271             case STATE_PREVIEW:
    272                 initializeEffect(true);
    273                 break;
    274         }
    275     }
    276 
    277     public void setEffect(int effect, Object effectParameter) {
    278         if (mLogVerbose) Log.v(TAG,
    279                                "setEffect: effect ID " + effect +
    280                                ", parameter " + effectParameter.toString() );
    281         switch (mState) {
    282             case STATE_RECORD:
    283                 throw new RuntimeException("setEffect cannot be called while recording!");
    284             case STATE_RELEASED:
    285                 throw new RuntimeException("setEffect called on an already released recorder!");
    286             default:
    287                 break;
    288         }
    289 
    290         mEffect = effect;
    291         mEffectParameter = effectParameter;
    292 
    293         if (mState == STATE_PREVIEW ||
    294                 mState == STATE_STARTING_PREVIEW) {
    295             initializeEffect(false);
    296         }
    297     }
    298 
    299     public interface EffectsListener {
    300         public void onEffectsUpdate(int effectId, int effectMsg);
    301         public void onEffectsError(Exception exception, String filePath);
    302     }
    303 
    304     public void setEffectsListener(EffectsListener listener) {
    305         mEffectsListener = listener;
    306     }
    307 
    308     private void setFaceDetectOrientation() {
    309         if (mCurrentEffect == EFFECT_GOOFY_FACE) {
    310             Filter rotateFilter = mRunner.getGraph().getFilter("rotate");
    311             Filter metaRotateFilter = mRunner.getGraph().getFilter("metarotate");
    312             rotateFilter.setInputValue("rotation", mOrientationHint);
    313             int reverseDegrees = (360 - mOrientationHint) % 360;
    314             metaRotateFilter.setInputValue("rotation", reverseDegrees);
    315         }
    316     }
    317 
    318     private void setRecordingOrientation() {
    319         if ( mState != STATE_RECORD && mRunner != null) {
    320             Point bl = new Point(0, 0);
    321             Point br = new Point(1, 0);
    322             Point tl = new Point(0, 1);
    323             Point tr = new Point(1, 1);
    324             Quad recordingRegion;
    325             if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
    326                 // The back camera is not mirrored, so use a identity transform
    327                 recordingRegion = new Quad(bl, br, tl, tr);
    328             } else {
    329                 // Recording region needs to be tweaked for front cameras, since they
    330                 // mirror their preview
    331                 if (mOrientationHint == 0 || mOrientationHint == 180) {
    332                     // Horizontal flip in landscape
    333                     recordingRegion = new Quad(br, bl, tr, tl);
    334                 } else {
    335                     // Horizontal flip in portrait
    336                     recordingRegion = new Quad(tl, tr, bl, br);
    337                 }
    338             }
    339             Filter recorder = mRunner.getGraph().getFilter("recorder");
    340             recorder.setInputValue("inputRegion", recordingRegion);
    341         }
    342     }
    343     public void setOrientationHint(int degrees) {
    344         switch (mState) {
    345             case STATE_RELEASED:
    346                 throw new RuntimeException(
    347                         "setOrientationHint called on an already released recorder!");
    348             default:
    349                 break;
    350         }
    351         if (mLogVerbose) Log.v(TAG, "Setting orientation hint to: " + degrees);
    352         mOrientationHint = degrees;
    353         setFaceDetectOrientation();
    354         setRecordingOrientation();
    355     }
    356 
    357     public void setCameraDisplayOrientation(int orientation) {
    358         if (mState != STATE_CONFIGURE) {
    359             throw new RuntimeException(
    360                 "setCameraDisplayOrientation called after configuration!");
    361         }
    362         mCameraDisplayOrientation = orientation;
    363     }
    364 
    365     public void setCameraFacing(int facing) {
    366         switch (mState) {
    367             case STATE_RELEASED:
    368                 throw new RuntimeException(
    369                     "setCameraFacing called on alrady released recorder!");
    370             default:
    371                 break;
    372         }
    373         mCameraFacing = facing;
    374         setRecordingOrientation();
    375     }
    376 
    377     public void setOnInfoListener(MediaRecorder.OnInfoListener infoListener) {
    378         switch (mState) {
    379             case STATE_RECORD:
    380                 throw new RuntimeException("setInfoListener cannot be called while recording!");
    381             case STATE_RELEASED:
    382                 throw new RuntimeException(
    383                     "setInfoListener called on an already released recorder!");
    384             default:
    385                 break;
    386         }
    387         mInfoListener = infoListener;
    388     }
    389 
    390     public void setOnErrorListener(MediaRecorder.OnErrorListener errorListener) {
    391         switch (mState) {
    392             case STATE_RECORD:
    393                 throw new RuntimeException("setErrorListener cannot be called while recording!");
    394             case STATE_RELEASED:
    395                 throw new RuntimeException(
    396                     "setErrorListener called on an already released recorder!");
    397             default:
    398                 break;
    399         }
    400         mErrorListener = errorListener;
    401     }
    402 
    403     private void initializeFilterFramework() {
    404         mGraphEnv = new GraphEnvironment();
    405         mGraphEnv.createGLEnvironment();
    406 
    407         int videoFrameWidth = mProfile.videoFrameWidth;
    408         int videoFrameHeight = mProfile.videoFrameHeight;
    409         if (mCameraDisplayOrientation == 90 || mCameraDisplayOrientation == 270) {
    410             int tmp = videoFrameWidth;
    411             videoFrameWidth = videoFrameHeight;
    412             videoFrameHeight = tmp;
    413         }
    414 
    415         mGraphEnv.addReferences(
    416                 "textureSourceCallback", mSourceReadyCallback,
    417                 "recordingWidth", videoFrameWidth,
    418                 "recordingHeight", videoFrameHeight,
    419                 "recordingProfile", mProfile,
    420                 "learningDoneListener", mLearningDoneListener,
    421                 "recordingDoneListener", mRecordingDoneListener);
    422         mRunner = null;
    423         mGraphId = -1;
    424         mCurrentEffect = EFFECT_NONE;
    425     }
    426 
    427     private synchronized void initializeEffect(boolean forceReset) {
    428         if (forceReset ||
    429             mCurrentEffect != mEffect ||
    430             mCurrentEffect == EFFECT_BACKDROPPER) {
    431 
    432             mGraphEnv.addReferences(
    433                     "previewSurfaceTexture", mPreviewSurfaceTexture,
    434                     "previewWidth", mPreviewWidth,
    435                     "previewHeight", mPreviewHeight,
    436                     "orientation", mOrientationHint);
    437             if (mState == STATE_PREVIEW ||
    438                     mState == STATE_STARTING_PREVIEW) {
    439                 // Switching effects while running. Inform video camera.
    440                 sendMessage(mCurrentEffect, EFFECT_MSG_SWITCHING_EFFECT);
    441             }
    442 
    443             switch (mEffect) {
    444                 case EFFECT_GOOFY_FACE:
    445                     mGraphId = mGraphEnv.loadGraph(mContext, R.raw.goofy_face);
    446                     break;
    447                 case EFFECT_BACKDROPPER:
    448                     sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING);
    449                     mGraphId = mGraphEnv.loadGraph(mContext, R.raw.backdropper);
    450                     break;
    451                 default:
    452                     throw new RuntimeException("Unknown effect ID" + mEffect + "!");
    453             }
    454             mCurrentEffect = mEffect;
    455 
    456             mOldRunner = mRunner;
    457             mRunner = mGraphEnv.getRunner(mGraphId, GraphEnvironment.MODE_ASYNCHRONOUS);
    458             mRunner.setDoneCallback(mRunnerDoneCallback);
    459             if (mLogVerbose) {
    460                 Log.v(TAG, "New runner: " + mRunner
    461                       + ". Old runner: " + mOldRunner);
    462             }
    463             if (mState == STATE_PREVIEW ||
    464                     mState == STATE_STARTING_PREVIEW) {
    465                 // Switching effects while running. Stop existing runner.
    466                 // The stop callback will take care of starting new runner.
    467                 mCameraDevice.stopPreview();
    468                 try {
    469                     mCameraDevice.setPreviewTexture(null);
    470                 } catch(IOException e) {
    471                     throw new RuntimeException("Unable to connect camera to effect input", e);
    472                 }
    473                 mOldRunner.stop();
    474             }
    475         }
    476 
    477         switch (mCurrentEffect) {
    478             case EFFECT_GOOFY_FACE:
    479                 tryEnableVideoStabilization(true);
    480                 Filter goofyFilter = mRunner.getGraph().getFilter("goofyrenderer");
    481                 goofyFilter.setInputValue("currentEffect",
    482                                           ((Integer)mEffectParameter).intValue());
    483                 break;
    484             case EFFECT_BACKDROPPER:
    485                 tryEnableVideoStabilization(false);
    486                 Filter backgroundSrc = mRunner.getGraph().getFilter("background");
    487                 backgroundSrc.setInputValue("sourceUrl", mEffectParameter);
    488                 // For front camera, the background video needs to be mirrored in the
    489                 // backdropper filter
    490                 if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    491                     Filter replacer = mRunner.getGraph().getFilter("replacer");
    492                     replacer.setInputValue("mirrorBg", true);
    493                     if (mLogVerbose) Log.v(TAG, "Setting the background to be mirrored");
    494                 }
    495                 break;
    496             default:
    497                 break;
    498         }
    499         setFaceDetectOrientation();
    500         setRecordingOrientation();
    501     }
    502 
    503     public synchronized void startPreview() {
    504         if (mLogVerbose) Log.v(TAG, "Starting preview (" + this + ")");
    505 
    506         switch (mState) {
    507             case STATE_STARTING_PREVIEW:
    508             case STATE_PREVIEW:
    509                 // Already running preview
    510                 Log.w(TAG, "startPreview called when already running preview");
    511                 return;
    512             case STATE_RECORD:
    513                 throw new RuntimeException("Cannot start preview when already recording!");
    514             case STATE_RELEASED:
    515                 throw new RuntimeException("setEffect called on an already released recorder!");
    516             default:
    517                 break;
    518         }
    519 
    520         if (mEffect == EFFECT_NONE) {
    521             throw new RuntimeException("No effect selected!");
    522         }
    523         if (mEffectParameter == null) {
    524             throw new RuntimeException("No effect parameter provided!");
    525         }
    526         if (mProfile == null) {
    527             throw new RuntimeException("No recording profile provided!");
    528         }
    529         if (mPreviewSurfaceTexture == null) {
    530             if (mLogVerbose) Log.v(TAG, "Passed a null surface; waiting for valid one");
    531             mState = STATE_WAITING_FOR_SURFACE;
    532             return;
    533         }
    534         if (mCameraDevice == null) {
    535             throw new RuntimeException("No camera to record from!");
    536         }
    537 
    538         if (mLogVerbose) Log.v(TAG, "Initializing filter framework and running the graph.");
    539         initializeFilterFramework();
    540 
    541         initializeEffect(true);
    542 
    543         mState = STATE_STARTING_PREVIEW;
    544         mRunner.run();
    545         // Rest of preview startup handled in mSourceReadyCallback
    546     }
    547 
    548     private SurfaceTextureSourceListener mSourceReadyCallback =
    549             new SurfaceTextureSourceListener() {
    550         @Override
    551         public void onSurfaceTextureSourceReady(SurfaceTexture source) {
    552             if (mLogVerbose) Log.v(TAG, "SurfaceTexture ready callback received");
    553             synchronized(EffectsRecorder.this) {
    554                 mTextureSource = source;
    555 
    556                 if (mState == STATE_CONFIGURE) {
    557                     // Stop preview happened while the runner was doing startup tasks
    558                     // Since we haven't started anything up, don't do anything
    559                     // Rest of cleanup will happen in onRunnerDone
    560                     if (mLogVerbose) Log.v(TAG, "Ready callback: Already stopped, skipping.");
    561                     return;
    562                 }
    563                 if (mState == STATE_RELEASED) {
    564                     // EffectsRecorder has been released, so don't touch the camera device
    565                     // or anything else
    566                     if (mLogVerbose) Log.v(TAG, "Ready callback: Already released, skipping.");
    567                     return;
    568                 }
    569                 if (source == null) {
    570                     if (mLogVerbose) {
    571                         Log.v(TAG, "Ready callback: source null! Looks like graph was closed!");
    572                     }
    573                     if (mState == STATE_PREVIEW ||
    574                             mState == STATE_STARTING_PREVIEW ||
    575                             mState == STATE_RECORD) {
    576                         // A null source here means the graph is shutting down
    577                         // unexpectedly, so we need to turn off preview before
    578                         // the surface texture goes away.
    579                         if (mLogVerbose) {
    580                             Log.v(TAG, "Ready callback: State: " + mState + ". stopCameraPreview");
    581                         }
    582 
    583                         stopCameraPreview();
    584                     }
    585                     return;
    586                 }
    587 
    588                 // Lock AE/AWB to reduce transition flicker
    589                 tryEnable3ALocks(true);
    590 
    591                 mCameraDevice.stopPreview();
    592                 if (mLogVerbose) Log.v(TAG, "Runner active, connecting effects preview");
    593                 try {
    594                     mCameraDevice.setPreviewTexture(mTextureSource);
    595                 } catch(IOException e) {
    596                     throw new RuntimeException("Unable to connect camera to effect input", e);
    597                 }
    598 
    599                 mCameraDevice.startPreview();
    600 
    601                 // Unlock AE/AWB after preview started
    602                 tryEnable3ALocks(false);
    603 
    604                 mState = STATE_PREVIEW;
    605 
    606                 if (mLogVerbose) Log.v(TAG, "Start preview/effect switch complete");
    607 
    608                 // Sending a message to listener that preview is complete
    609                 sendMessage(mCurrentEffect, EFFECT_MSG_PREVIEW_RUNNING);
    610             }
    611         }
    612     };
    613 
    614     private LearningDoneListener mLearningDoneListener =
    615             new LearningDoneListener() {
    616         @Override
    617         public void onLearningDone(BackDropperFilter filter) {
    618             if (mLogVerbose) Log.v(TAG, "Learning done callback triggered");
    619             // Called in a processing thread, so have to post message back to UI
    620             // thread
    621             sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_DONE_LEARNING);
    622             enable3ALocks(true);
    623         }
    624     };
    625 
    626     // A callback to finalize the media after the recording is done.
    627     private OnRecordingDoneListener mRecordingDoneListener =
    628             new OnRecordingDoneListener() {
    629         // Forward the callback to the VideoCamera object (as an asynchronous event).
    630         @Override
    631         public void onRecordingDone() {
    632             if (mLogVerbose) Log.v(TAG, "Recording done callback triggered");
    633             sendMessage(EFFECT_NONE, EFFECT_MSG_RECORDING_DONE);
    634         }
    635     };
    636 
    637     public synchronized void startRecording() {
    638         if (mLogVerbose) Log.v(TAG, "Starting recording (" + this + ")");
    639 
    640         switch (mState) {
    641             case STATE_RECORD:
    642                 throw new RuntimeException("Already recording, cannot begin anew!");
    643             case STATE_RELEASED:
    644                 throw new RuntimeException(
    645                     "startRecording called on an already released recorder!");
    646             default:
    647                 break;
    648         }
    649 
    650         if ((mOutputFile == null) && (mFd == null)) {
    651             throw new RuntimeException("No output file name or descriptor provided!");
    652         }
    653 
    654         if (mState == STATE_CONFIGURE) {
    655             startPreview();
    656         }
    657 
    658         Filter recorder = mRunner.getGraph().getFilter("recorder");
    659         if (mFd != null) {
    660             recorder.setInputValue("outputFileDescriptor", mFd);
    661         } else {
    662             recorder.setInputValue("outputFile", mOutputFile);
    663         }
    664         // It is ok to set the audiosource without checking for timelapse here
    665         // since that check will be done in the MediaEncoderFilter itself
    666         recorder.setInputValue("audioSource", MediaRecorder.AudioSource.CAMCORDER);
    667 
    668         recorder.setInputValue("recordingProfile", mProfile);
    669         recorder.setInputValue("orientationHint", mOrientationHint);
    670         // Important to set the timelapseinterval to 0 if the capture rate is not >0
    671         // since the recorder does not get created every time the recording starts.
    672         // The recorder infers whether the capture is timelapsed based on the value of
    673         // this interval
    674         boolean captureTimeLapse = mCaptureRate > 0;
    675         if (captureTimeLapse) {
    676             double timeBetweenFrameCapture = 1 / mCaptureRate;
    677             recorder.setInputValue("timelapseRecordingIntervalUs",
    678                     (long) (1000000 * timeBetweenFrameCapture));
    679         } else {
    680             recorder.setInputValue("timelapseRecordingIntervalUs", 0L);
    681         }
    682 
    683         if (mInfoListener != null) {
    684             recorder.setInputValue("infoListener", mInfoListener);
    685         }
    686         if (mErrorListener != null) {
    687             recorder.setInputValue("errorListener", mErrorListener);
    688         }
    689         recorder.setInputValue("maxFileSize", mMaxFileSize);
    690         recorder.setInputValue("maxDurationMs", mMaxDurationMs);
    691         recorder.setInputValue("recording", true);
    692         mCameraSound.play(MediaActionSound.START_VIDEO_RECORDING);
    693         mState = STATE_RECORD;
    694     }
    695 
    696     public synchronized void stopRecording() {
    697         if (mLogVerbose) Log.v(TAG, "Stop recording (" + this + ")");
    698 
    699         switch (mState) {
    700             case STATE_CONFIGURE:
    701             case STATE_STARTING_PREVIEW:
    702             case STATE_PREVIEW:
    703                 Log.w(TAG, "StopRecording called when recording not active!");
    704                 return;
    705             case STATE_RELEASED:
    706                 throw new RuntimeException("stopRecording called on released EffectsRecorder!");
    707             default:
    708                 break;
    709         }
    710         Filter recorder = mRunner.getGraph().getFilter("recorder");
    711         recorder.setInputValue("recording", false);
    712         mCameraSound.play(MediaActionSound.STOP_VIDEO_RECORDING);
    713         mState = STATE_PREVIEW;
    714     }
    715 
    716     // Called to tell the filter graph that the display surfacetexture is not valid anymore.
    717     // So the filter graph should not hold any reference to the surface created with that.
    718     public synchronized void disconnectDisplay() {
    719         if (mLogVerbose) Log.v(TAG, "Disconnecting the graph from the " +
    720             "SurfaceTexture");
    721         SurfaceTextureTarget display = (SurfaceTextureTarget)
    722             mRunner.getGraph().getFilter("display");
    723         display.disconnect(mGraphEnv.getContext());
    724     }
    725 
    726     // The VideoCamera will call this to notify that the camera is being
    727     // released to the outside world. This call should happen after the
    728     // stopRecording call. Else, the effects may throw an exception.
    729     // With the recording stopped, the stopPreview call will not try to
    730     // release the camera again.
    731     // This must be called in onPause() if the effects are ON.
    732     public synchronized void disconnectCamera() {
    733         if (mLogVerbose) Log.v(TAG, "Disconnecting the effects from Camera");
    734         stopCameraPreview();
    735         mCameraDevice = null;
    736     }
    737 
    738     // In a normal case, when the disconnect is not called, we should not
    739     // set the camera device to null, since on return callback, we try to
    740     // enable 3A locks, which need the cameradevice.
    741     public synchronized void stopCameraPreview() {
    742         if (mLogVerbose) Log.v(TAG, "Stopping camera preview.");
    743         if (mCameraDevice == null) {
    744             Log.d(TAG, "Camera already null. Nothing to disconnect");
    745             return;
    746         }
    747         mCameraDevice.stopPreview();
    748         try {
    749             mCameraDevice.setPreviewTexture(null);
    750         } catch(IOException e) {
    751             throw new RuntimeException("Unable to disconnect camera");
    752         }
    753     }
    754 
    755     // Stop and release effect resources
    756     public synchronized void stopPreview() {
    757         if (mLogVerbose) Log.v(TAG, "Stopping preview (" + this + ")");
    758         switch (mState) {
    759             case STATE_CONFIGURE:
    760                 Log.w(TAG, "StopPreview called when preview not active!");
    761                 return;
    762             case STATE_RELEASED:
    763                 throw new RuntimeException("stopPreview called on released EffectsRecorder!");
    764             default:
    765                 break;
    766         }
    767 
    768         if (mState == STATE_RECORD) {
    769             stopRecording();
    770         }
    771 
    772         mCurrentEffect = EFFECT_NONE;
    773 
    774         // This will not do anything if the camera has already been disconnected.
    775         stopCameraPreview();
    776 
    777         mState = STATE_CONFIGURE;
    778         mOldRunner = mRunner;
    779         mRunner.stop();
    780         mRunner = null;
    781         // Rest of stop and release handled in mRunnerDoneCallback
    782     }
    783 
    784     // Try to enable/disable video stabilization if supported; otherwise return false
    785     // It is called from a synchronized block.
    786     boolean tryEnableVideoStabilization(boolean toggle) {
    787         if (mLogVerbose) Log.v(TAG, "tryEnableVideoStabilization.");
    788         if (mCameraDevice == null) {
    789             Log.d(TAG, "Camera already null. Not enabling video stabilization.");
    790             return false;
    791         }
    792         Camera.Parameters params = mCameraDevice.getParameters();
    793 
    794         String vstabSupported = params.get("video-stabilization-supported");
    795         if ("true".equals(vstabSupported)) {
    796             if (mLogVerbose) Log.v(TAG, "Setting video stabilization to " + toggle);
    797             params.set("video-stabilization", toggle ? "true" : "false");
    798             mCameraDevice.setParameters(params);
    799             return true;
    800         }
    801         if (mLogVerbose) Log.v(TAG, "Video stabilization not supported");
    802         return false;
    803     }
    804 
    805     // Try to enable/disable 3A locks if supported; otherwise return false
    806     synchronized boolean tryEnable3ALocks(boolean toggle) {
    807         if (mLogVerbose) Log.v(TAG, "tryEnable3ALocks");
    808         if (mCameraDevice == null) {
    809             Log.d(TAG, "Camera already null. Not tryenabling 3A locks.");
    810             return false;
    811         }
    812         Camera.Parameters params = mCameraDevice.getParameters();
    813         if (params.isAutoExposureLockSupported() &&
    814             params.isAutoWhiteBalanceLockSupported() ) {
    815             params.setAutoExposureLock(toggle);
    816             params.setAutoWhiteBalanceLock(toggle);
    817             mCameraDevice.setParameters(params);
    818             return true;
    819         }
    820         return false;
    821     }
    822 
    823     // Try to enable/disable 3A locks if supported; otherwise, throw error
    824     // Use this when locks are essential to success
    825     synchronized void enable3ALocks(boolean toggle) {
    826         if (mLogVerbose) Log.v(TAG, "Enable3ALocks");
    827         if (mCameraDevice == null) {
    828             Log.d(TAG, "Camera already null. Not enabling 3A locks.");
    829             return;
    830         }
    831         Camera.Parameters params = mCameraDevice.getParameters();
    832         if (!tryEnable3ALocks(toggle)) {
    833             throw new RuntimeException("Attempt to lock 3A on camera with no locking support!");
    834         }
    835     }
    836 
    837     private OnRunnerDoneListener mRunnerDoneCallback =
    838             new OnRunnerDoneListener() {
    839         @Override
    840         public void onRunnerDone(int result) {
    841             synchronized(EffectsRecorder.this) {
    842                 if (mLogVerbose) {
    843                     Log.v(TAG,
    844                           "Graph runner done (" + EffectsRecorder.this
    845                           + ", mRunner " + mRunner
    846                           + ", mOldRunner " + mOldRunner + ")");
    847                 }
    848                 if (result == GraphRunner.RESULT_ERROR) {
    849                     // Handle error case
    850                     Log.e(TAG, "Error running filter graph!");
    851                     Exception e = null;
    852                     if (mRunner != null) {
    853                         e = mRunner.getError();
    854                     } else if (mOldRunner != null) {
    855                         e = mOldRunner.getError();
    856                     }
    857                     raiseError(e);
    858                 }
    859                 if (mOldRunner != null) {
    860                     // Tear down old graph if available
    861                     if (mLogVerbose) Log.v(TAG, "Tearing down old graph.");
    862                     GLEnvironment glEnv = mGraphEnv.getContext().getGLEnvironment();
    863                     if (glEnv != null && !glEnv.isActive()) {
    864                         glEnv.activate();
    865                     }
    866                     mOldRunner.getGraph().tearDown(mGraphEnv.getContext());
    867                     if (glEnv != null && glEnv.isActive()) {
    868                         glEnv.deactivate();
    869                     }
    870                     mOldRunner = null;
    871                 }
    872                 if (mState == STATE_PREVIEW ||
    873                         mState == STATE_STARTING_PREVIEW) {
    874                     // Switching effects, start up the new runner
    875                     if (mLogVerbose) {
    876                         Log.v(TAG, "Previous effect halted. Running graph again. state: " + mState);
    877                     }
    878                     tryEnable3ALocks(false);
    879                     // In case of an error, the graph restarts from beginning and in case
    880                     // of the BACKDROPPER effect, the learner re-learns the background.
    881                     // Hence, we need to show the learning dialogue to the user
    882                     // to avoid recording before the learning is done. Else, the user
    883                     // could start recording before the learning is done and the new
    884                     // background comes up later leading to an end result video
    885                     // with a heterogeneous background.
    886                     // For BACKDROPPER effect, this path is also executed sometimes at
    887                     // the end of a normal recording session. In such a case, the graph
    888                     // does not restart and hence the learner does not re-learn. So we
    889                     // do not want to show the learning dialogue then.
    890                     if (result == GraphRunner.RESULT_ERROR &&
    891                             mCurrentEffect == EFFECT_BACKDROPPER) {
    892                         sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING);
    893                     }
    894                     mRunner.run();
    895                 } else if (mState != STATE_RELEASED) {
    896                     // Shutting down effects
    897                     if (mLogVerbose) Log.v(TAG, "Runner halted, restoring direct preview");
    898                     tryEnable3ALocks(false);
    899                     sendMessage(EFFECT_NONE, EFFECT_MSG_EFFECTS_STOPPED);
    900                 } else {
    901                     // STATE_RELEASED - camera will be/has been released as well, do nothing.
    902                 }
    903             }
    904         }
    905     };
    906 
    907     // Indicates that all camera/recording activity needs to halt
    908     public synchronized void release() {
    909         if (mLogVerbose) Log.v(TAG, "Releasing (" + this + ")");
    910 
    911         switch (mState) {
    912             case STATE_RECORD:
    913             case STATE_STARTING_PREVIEW:
    914             case STATE_PREVIEW:
    915                 stopPreview();
    916                 // Fall-through
    917             default:
    918                 if (mCameraSound != null) {
    919                     mCameraSound.release();
    920                     mCameraSound = null;
    921                 }
    922                 mState = STATE_RELEASED;
    923                 break;
    924         }
    925     }
    926 
    927     private void sendMessage(final int effect, final int msg) {
    928         if (mEffectsListener != null) {
    929             mHandler.post(new Runnable() {
    930                 @Override
    931                 public void run() {
    932                     mEffectsListener.onEffectsUpdate(effect, msg);
    933                 }
    934             });
    935         }
    936     }
    937 
    938     private void raiseError(final Exception exception) {
    939         if (mEffectsListener != null) {
    940             mHandler.post(new Runnable() {
    941                 @Override
    942                 public void run() {
    943                     if (mFd != null) {
    944                         mEffectsListener.onEffectsError(exception, null);
    945                     } else {
    946                         mEffectsListener.onEffectsError(exception, mOutputFile);
    947                     }
    948                 }
    949             });
    950         }
    951     }
    952 
    953 }
    954