• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
isEffectSupported(int effectId)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 
EffectsRecorder(Context context)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 
setCamera(Camera cameraDevice)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 
setProfile(CamcorderProfile profile)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 
setOutputFile(String outputFile)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 
setOutputFile(FileDescriptor fd)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     */
setMaxFileSize(long maxFileSize)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     */
setMaxDuration(int maxDurationMs)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 
setCaptureRate(double fps)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 
setPreviewSurfaceTexture(SurfaceTexture previewSurfaceTexture, int previewWidth, int previewHeight)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 
setEffect(int effect, Object effectParameter)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 {
onEffectsUpdate(int effectId, int effectMsg)300         public void onEffectsUpdate(int effectId, int effectMsg);
onEffectsError(Exception exception, String filePath)301         public void onEffectsError(Exception exception, String filePath);
302     }
303 
setEffectsListener(EffectsListener listener)304     public void setEffectsListener(EffectsListener listener) {
305         mEffectsListener = listener;
306     }
307 
setFaceDetectOrientation()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 
setRecordingOrientation()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     }
setOrientationHint(int degrees)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 
setCameraDisplayOrientation(int orientation)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 
setCameraFacing(int facing)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 
setOnInfoListener(MediaRecorder.OnInfoListener infoListener)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 
setOnErrorListener(MediaRecorder.OnErrorListener errorListener)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 
initializeFilterFramework()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 
initializeEffect(boolean forceReset)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 
startPreview()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 
startRecording()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 
stopRecording()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.
disconnectDisplay()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.
disconnectCamera()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.
stopCameraPreview()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
stopPreview()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.
tryEnableVideoStabilization(boolean toggle)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
tryEnable3ALocks(boolean toggle)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
enable3ALocks(boolean toggle)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
release()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 
sendMessage(final int effect, final int msg)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 
raiseError(final Exception exception)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